# Joining Ad Note
Source: https://docs.adrop.io/ad-platform/ad-note
List your Ad Products on Ad Note to connect with more advertisers and enable real-time bookings.
***
## What is Ad Note?
**Ad Note** is an advertiser community platform where publishers can list their advertising inventory. By joining Ad Note, your Ad Products gain visibility in the marketplace, enabling discovery by more advertisers.
### Benefits of Joining
| Benefit | Description |
| - | - |
| **Increased Visibility** | Your Ad Products are exposed to advertisers in the Ad Note community |
| **AI Agent Support** | Adrop's AI agent analyzes your Ad Product brochure and automatically recommends products to advertisers |
| **Real-time Booking** | Advertisers can book campaigns directly through Ad Note |
| **Unified Management** | Profile updates in Console sync to Ad Note in real-time |
Campaigns executed through Ad Note are subject to agency commission fees per the Ad Control Terms of Service.
***
## Requirements
Before applying, ensure you have:
* An active Ad Control Console account
* Completed business information and payout account setup
* Ad Product public registration: Set Ad Products you want to sell through Ad Note to 'public' status, and set products excluded from sale to 'private'.
All Ad Control plans, including the free Stream plan, are eligible to join Ad Note. However, to offer instant booking to advertisers, you need a Wave plan or higher and at least one published Ad Product.
***
## How to Join
Click the **Join Ad Note** button in the Settings menu sidebar to view a checklist of required items. Complete each item to enable your application.
### Checklist
Register your business information and payout account for receiving Ad Note campaign revenue.
1. Click **Settings** in the left menu.
2. Select the **Revenue Payout** tab.
3. Enter the following information:
**Business Information**
* Business name
* Business registration number
* Business registration certificate file
* Billing contact email
**Payout Account**
* Account holder name
* Bank
* Account number
* Bank statement copy
View detailed setup instructions
Configure your Ad Platform information and service details that will be displayed to advertisers.
1. Click **Settings** in the left menu.
2. Select the **Ad Profile** tab.
#### Ad Platform Information
| Field | Description |
| - | - |
| **Ad Platform Name** | Name displayed to advertisers |
| **Logo** | Ad Platform logo image |
| **Brand Color** | Color applied to buttons and backgrounds |
| **Thumbnail** | Image displayed when links are shared |
| **Primary Email** | Email for advertiser inquiries |
| **Regional Settings** | Country, timezone, language |
#### Service Information
**Ad Note Brochure Upload**
Upload a PDF brochure for advertisers. The AI agent will analyze this to automatically recommend products to advertisers.
| Guideline | Description |
| - | - |
| **No Direct Contact Info** | Use only `contact@adrop.io` for contact information |
| **Consistent Product Names** | Match brochure product names with Console product names |
| **Remove Confidential Info** | Exclude any information that cannot be shared externally |
| **No Individual Policies/Processes** | Exclude individual publisher policies and custom booking processes |
**Service Description**
| Field | Description |
| - | - |
| **Category** | Select the category that best fits your service |
| **One-Line Description** | Used as the title for your Ad Product introduction. Highlight your service's differentiated reach and core target audience strengths |
| **Detailed Description** | Describe the campaign types your service is particularly well-suited for |
Use the Preview button to verify how your information displays on the Ad Platform and Ad Note.
***
## Submit Application
Once all checklist items are complete, the **Join** button will be enabled.
1. Complete all checklist items.
2. Click the **Join** button.
3. Our team will review your application.
4. Once approved, your Ad Products will automatically appear in the Ad Note community.
When your application is submitted, you'll see the message: "Your Ad Note application has been submitted. Your listing will appear on Ad Note after review is complete."
***
## After Joining
### Real-time Sync
Profile updates in Console sync to Ad Note instantly. Changes to product information, service descriptions, or promotional materials take immediate effect.
### Promotion Management
You can create promotions to attract advertisers. They will be posted on the promotions page when you join Ad Note.
Learn how to set up discounts and free trials
***
## Operating Policy
The following policies apply to campaign booking, approval, and settlement through Ad Note.
### Campaign Terms
Advertisers book campaigns based on the [**Ad.Note Campaign Terms**](/legal/adnote-campaign-terms), not individual publisher policies. Agency commission policies and cancellation penalty policies also follow Ad Note's common standards.
### Campaign and Creative Approval
| Item | Description |
| - | - |
| **Ad Note Campaign Indicator** | Campaigns with an A.drop logo are booked through Ad Note |
| **Review Obligation** | Publishers must review and approve campaigns and creatives in the Console |
| **Review Deadline** | [Campaign review deadlines](/management/review#review-deadline) and creative review (within 48 hours) must be followed |
### Cancellation Penalties
Campaign booking is confirmed upon publisher approval. If an approved campaign is cancelled due to unilateral reasons by the publisher, cancellation penalties will be charged according to the [Ad.Note Campaign Terms](/legal/adnote-campaign-terms).
If campaign rejection, approval delays, or unilateral contract termination occur repeatedly without legitimate reasons such as internal policies or legal issues, it may affect ad product sales and result in Ad Note listing being restricted or rejected.
### Ad Spend Settlement
Ad spend settlement is handled directly between Adrop and the advertiser. Agency commission (incentive) policies are applied flexibly according to Ad Note policies.
### Fee Information
**Sales Commission**
For campaigns connected through Ad Note, revenue is paid after deducting a 20% commission from ad spend. However, if there is a separate sales partnership agreement, that contract's commission rate takes precedence.
**Card Payment Fees**
When advertisers pay by card, payment processing fees are charged separately.
| Payment Region | PG Provider | Fee |
| - | - | - |
| Domestic (Korea) | Toss Payments | 3.2% |
| International | Stripe | 5.5% |
***
## Next Steps
Set up Ad Units to include in your Ad Products
***
## FAQ
You need an active Ad Control Console account with completed business information and payout account. All plans including the free Stream plan are eligible. To offer instant booking, you need a Wave plan or higher and at least one published Ad Product.
Include your Ad Product lineup, pricing, expected performance, and creative guidelines. Avoid direct contact information (use `contact@adrop.io` for ad inquiries), confidential data, and individual publisher policies.
Changes apply instantly. When you modify your profile in Console settings, it is immediately reflected in the Ad Note community.
Campaigns executed through Ad Note are subject to agency commission fees per the Ad Control Terms of Service.
Applications may be rejected if the submitted information doesn't meet our review criteria. Check the rejection reason, make necessary corrections, and resubmit your application.
# Creating an Ad Platform
Source: https://docs.adrop.io/ad-platform/create
A guide to creating an Ad Platform.
## Creation Method
Click on the project name in the top left
Click **\[Deploy new Ad Platform]** at the bottom of the dropdown menu
***
## Service Name
* Enter your official name (2-20 characters)
* Korean, English, and numbers are allowed
* Display it clearly as it will be shown to advertisers
## Logo and Theme Color
* **Format**: PNG or SVG recommended
* **Size**: 256px or larger
* **Theme Color**: Automatically extracted from the logo and applied throughout the Ad Platform
Border correction is recommended if the logo background color has low contrast
## Subdomain
This is the link to share with advertisers.
* **Allowed characters**: Lowercase letters, numbers, hyphen (-)
* **Final link**: `https://{your-input}.adrop.io`
* **No duplicates**: Cannot use domains already in use by other publishers
* **Modifiable**: Can be changed after creation
## Operating Region Settings
| Item | Description |
| - | - |
| **Country** | Select the accounting base region. Used for Settlement and business information, and currency follows the country default. |
| **Timezone** | The timezone applied when booking Campaigns or viewing reports in the Console. |
| **Language** | The language used in the Console. |
Advertisers will see the timezone and language according to their own settings.
Register only a single service per Ad Platform. While platform-specific apps (Android/iOS/Web) can be operated together, mixing different services will affect targeting data accuracy.
***
## Next Steps
Set up branding and service information
***
## FAQ
Yes. You can create separate Ad Platforms for each service. Each platform has independent branding, Ad Products, and Settlement systems.
By default, it's provided in the format `{subdomain}.adrop.io`. If you need a custom domain, please contact us separately.
Yes. When you share the Ad Platform link, advertisers can directly access it to review products, book Campaigns, register Creatives, and make payments in a self-service manner.
# Dashboard
Source: https://docs.adrop.io/ad-platform/dashboard
You can view the overall status of your Ad Platform at a glance.
## How to Use
Click **\[Dashboard]** in the left menu
Select the month to view at the top
***
## Campaign Status
Campaign Booking and Revenue status for the selected month.
| Metric | Description |
| - | - |
| **New Campaigns** | Number of Campaigns newly booked in the month |
| **Total Booking Amount** | Total Booking amount for new Campaigns |
| **Closed Campaigns** | Number of Campaigns closed in the month |
| **Estimated Revenue / Revenue** | Estimated Settlement amount for closed Campaigns (current month shows 'Estimated Revenue', past shows 'Revenue') |
When viewing the current month, the change rate compared to the previous month is also displayed.
## Campaign Performance
Ad impression and click performance for the selected month.
| Metric | Description |
| - | - |
| **Total Impressions** | Number of times ads were displayed on screen |
| **Total Clicks** | Number of times ads were clicked |
| **Average CTR** | (Clicks ÷ Impressions) × 100 |
Impressions and clicks are each counted at most once per ad request, following industry-standard measurement guidelines.
## Backfill Ads
Revenue status for Backfill ads.
| Metric | Description |
| - | - |
| **Estimated Revenue** | Estimated Revenue from Backfill ads (in USD) |
| **Total Impressions** | Backfill ad impression count |
Backfill ad impressions are not included in Ad Control usage and do not incur additional charges.
## Notifications
Items requiring action are displayed at the top of the Dashboard.
| Notification | Description |
| - | - |
| **Campaign review needed** | There are Campaigns awaiting approval. |
| **Creative review needed** | There are Creatives awaiting approval. |
| **Campaigns need required Creatives** | There are Campaigns without registered Creatives. |
| **Campaign starting soon** | There are Campaigns starting soon. |
| **Booking not completed due to payment failure** | Payment retry is required. |
***
## Next Steps
Register Settlement information.
***
## FAQ
It's a feature that automatically fills inventory not served with direct deals with network ads to maintain a Fill Rate close to 100%. You can maintain direct deal Revenue while monetizing remaining traffic.
Generally, direct deals record CPMs 2-10 times higher than network ads. Ad Control prioritizes direct deals and fills only remaining inventory with Backfill to maximize overall Revenue.
Dashboard data is updated approximately every hour. If you need near real-time performance tracking, you can query more detailed data in the Reports menu.
# Member Management
Source: https://docs.adrop.io/ad-platform/members
Invite team Members and manage permissions.
## Setup Method
Click **\[Settings]** in the left menu
Select the **\[Members]** tab
***
## Inviting Members
Click the **\[Add Member]** button
Enter the email of the Member to invite (separate with Enter or comma to invite multiple)
Select the permission to grant
Click the **\[Invite]** button
## Member Roles
| Role | Description |
| - | - |
| **Owner** | Has all permissions in the Console. |
| **Editor** | Has editing permissions except for all deletion operations, plan changes, and Member Management. |
| **Viewer** | Can view all data in the Console. |
| **Guest** | Can only view Dashboard and Campaign ad performance. |
## Permission Details
| Permission | Owner | Editor | Viewer | Guest |
| - | :-: | :-: | :-: | :-: |
| **View Dashboard and Campaign information** | ✔ | ✔ | ✔ | ✔ |
| **All other viewing functions** | ✔ | ✔ | ✔ | |
| **Add/modify general information in management menu** | ✔ | ✔ | | |
| **Add/modify payment methods and business information** | ✔ | ✔ | | |
| **Delete payment methods** | ✔ | | | |
| **Change Console plan** | ✔ | | | |
| **Add/modify apps, Units, products, Targeting** | ✔ | ✔ | | |
| **Delete apps, Units, products, Targeting** | ✔ | | | |
| **Book/modify/review/pause/restart Campaigns** | ✔ | ✔ | | |
| **Delete Campaigns** | ✔ | | | |
| **Create/modify/review/pause/restart Creatives** | ✔ | ✔ | | |
| **Delete Creatives** | ✔ | | | |
| **Invite Members and change permissions** | ✔ | | | |
| **Delete Ad Platform** | ✔ | | | |
A project requires at least one Owner.
***
## Next Steps
List your Ad Products on Ad Note to connect with more advertisers
***
## FAQ
Yes. You can operate the Ad Platform as a team by assigning roles to Members. The advertising operations team can manage Campaigns with Editor permissions, and executives can monitor performance with Viewer permissions, enabling role division.
# What is an Ad Platform?
Source: https://docs.adrop.io/ad-platform/overview
An Ad Platform is a professional sales channel with your service brand. Advertisers can directly access it to review products and book campaigns.
## Ad Control Structure
Ad Control is designed to allow publishers and advertisers to operate advertising in environments suited to their respective roles.
The admin environment where publishers manage their advertising business.
* Create/manage Ad Products
* Configure Ad Units
* Review/approve Campaigns
* Analyze reports
* Manage Settlement
A self-serve environment where advertisers book Campaigns. A dedicated URL with the publisher's brand is provided.
* Browse Ad Products
* Book/pay for Campaigns
* Register Creatives
* View performance
A marketplace where you can promote Ad Products and attract advertisers.
* Ad Product brochures are exposed to advertisers when registered
* AI agents automatically respond to advertiser inquiries
* You can attract advertisers by registering promotions and benefits
***
## Table of Contents
1. ⚡ [Quickstart: 3-Minute Deploy](/ad-platform/quickstart)
2. [Creating an Ad Platform](/ad-platform/create)
3. [Configuring the Ad Platform](/ad-platform/settings)
4. [Dashboard](/ad-platform/dashboard)
5. [Revenue Payout](/ad-platform/payout)
6. [Member Management](/ad-platform/members)
7. [Joining Ad Note](/ad-platform/ad-note)
# Revenue Payout
Source: https://docs.adrop.io/ad-platform/payout
Register business information and Revenue Payout account for receiving Settlement.
## Setup Method
Click **\[Settings]** in the left menu
Select the **\[Revenue Payout]** tab
***
## Business Information
Information required for issuing Settlement statements and tax invoices.
| Item | Description |
| - | - |
| **Business Name** | Company name to be displayed on tax invoices |
| **TAX ID** | Business registration number or tax identification number |
| **Business Registration** | File upload |
| **Settlement Primary Email** | Email to receive tax invoice issuance and receipt notifications |
| **Settlement CC Email** | Additional emails to receive as CC (comma-separated) |
* Korea: Business registration number
* USA: EIN / TIN
* EU: VAT Number
* Japan: 法人番号
* India: GSTIN / PAN
## Revenue Payout Account
Account information to receive Settlement payments.
| Item | Description |
| - | - |
| **Account Holder Name** | Account owner's name |
| **Bank** | Select bank |
| **Account Number** | Account for Settlement deposit |
| **Bankbook Copy** | Upload image for account verification |
| **SWIFT Code** | Required for international transfers (not needed for domestic accounts) |
***
## Revenue Settlements
You can view revenue settlement dates, final settlement amounts, and settlements.
***
## Next Steps
Invite team Members and set permissions
***
## FAQ
Card payment Campaigns are automatically settled monthly. Settlement statements are issued after Campaign completion, and Revenue is paid to the registered account.
Card payment means Ad Control receives payment from the Advertiser and automatically processes Settlement. Manual payment means the Publisher directly settles with the Advertiser.
Yes. International payments are processed through Stripe, and international payment processing fees and country-specific withholding taxes may apply.
# Quickstart: 3-Minute Deploy
Source: https://docs.adrop.io/ad-platform/quickstart
A guide to deploying a branded Ad Platform in 3 minutes.
Time required: Approximately 3 minutes
## Before You Start
* Service name (2-20 characters)
* Logo image (PNG/SVG, 256px or larger)
## Launch the Launcher
Log in to the [Ad Control Console](https://console.adrop.io).
* **New signup**: The launcher will automatically appear if you don't have a project.
* **If you have an existing project**: Click on the project name in the top left → Click **\[Deploy new Ad Platform]**
* **Direct access**: [console.adrop.io/launcher](https://console.adrop.io/launcher)
## Step-by-Step Guide
2-20 characters, Korean/English/numbers
PNG/SVG, 256px or larger
Example: `myservice` → `https://myservice.adrop.io`
Country, timezone, language
Click the **\[Create]** button to complete
## Complete
Once creation is complete, your Ad Platform link will be issued.
Share this link with advertisers so they can directly book Campaigns.
## Next Steps
**Learn More**
Review each configuration item in detail.
**Move to Next Quickstart**
Step-by-step guide to creating Ad Units.
***
## FAQ
Building your own requires months of development resources, maintenance costs, and payment system integration. Ad Control allows you to deploy a branded Ad Platform in 3 minutes and start advertising sales immediately.
Yes. Ad Control can operate both direct deals and network ads (Backfill), allowing you to secure high CPMs with direct deals and automatically monetize remaining inventory.
# Configuring the Ad Platform
Source: https://docs.adrop.io/ad-platform/settings
You can modify the information of your created Ad Platform.
## How to Modify
Click **\[Settings]** in the left menu
Select the **\[Ad Profile]** tab
Click **\[Save]** after modifying information
***
## Ad Platform Information
### Basic Information
| Item | Description |
| - | - |
| **Ad Platform ID** | Unique identifier for the Ad Platform |
| **Ad Platform Name** | The Ad Platform name displayed to advertisers |
| **Primary Link** | The URL advertisers use to access the Ad Platform (subdomain) |
Sharing your Ad Platform ID when contacting support helps us assist you faster.
### Theme
| Item | Description |
| - | - |
| **App Image** | 256x256px or larger, JPG/PNG/WebP supported |
| **Brand Color** | Color applied to buttons and backgrounds. Very light colors are not recommended |
| **Thumbnail** | Image displayed when sharing links. 600x315px or larger |
### Contact Information
| Item | Description |
| - | - |
| **Primary Email** | Email to receive all inquiries and notification emails |
### Regional Settings
| Item | Description |
| - | - |
| **Country** | The accounting base region. Used for Settlement and business information, and currency follows the country default. |
| **Timezone** | The timezone applied for Campaign Booking and reports. |
| **Language** | The language used in the Console. |
Advertisers will see the timezone and language according to their own settings.
***
## Service Information
### Ad Product Brochure
You can upload an Ad Product brochure as a PDF. It will be used for Ad Product information on the Ad Platform and Ad Note.
When a brochure for Ad Note is uploaded, the **Adrop AI Agent** analyzes the content to automatically respond to advertiser inquiries and recommend suitable products.
#### Precautions for Ad Note Registration
| Item | Description |
| - | - |
| **Contact** | Do not include direct contact information. Use only `contact@adrop.io` |
| **Product Name** | Modify the product name in the brochure to match the product name in the Console |
| **Private Information** | Exclude all information that cannot be shared externally |
| **Policies** | Exclude individual publisher policies that differ from official policies and publisher-specific Booking processes |
### Service Description
Used for service information on the Ad Platform and Ad Note.
| Item | Description |
| - | - |
| **One-Line Description** | Express your service's differentiated reach and core target strengths. |
| **Detailed Description** | Explain which Campaigns your service is particularly well-suited for. |
***
## Next Steps
Check your Ad Platform status.
***
## FAQ
Ad Note is a platform where advertisers can explore and compare Ad Products from various publishers. When you register an Ad Product brochure, the Adrop AI Agent automatically responds to advertiser inquiries and recommends suitable products.
Yes. You can operate an Ad Platform without a brochure. However, we recommend registering a brochure to utilize advertiser acquisition through Ad Note and the AI automatic response feature.
Yes. Ad Control supports Audience Targeting using publishers' first-party data. Precise targeting is possible even without third-party cookies.
# Advanced Settings
Source: https://docs.adrop.io/ad-product/advanced
Learn about advanced ad product settings and troubleshooting.
***
## Frequency Cap
Set the maximum number of times campaigns from the same ad product can be shown to a single user per day.
### How to Set
Configure in the **Advanced Settings** section when creating or editing an ad product.
| Item | Description |
| - | - |
| **Frequency Cap** | Maximum impressions per user per day (default: 50) |
### Setting Guide
* **Branding ads**: Set frequency high to increase exposure opportunities.
* **Conversion ads**: Limit appropriately to reduce user fatigue.
Seeing the same ad too often causes user fatigue and decreases CTR. However, too few impressions reduce ad effectiveness.
***
## Private Ad Products
Ad products that are not publicly displayed on the ad platform and are only provided to specific advertisers.
### How to Set
Check **Private** in the **Product Info** section when creating an ad product.
### Use Cases
* Internal testing products
* Custom products for specific advertisers only
* Products not yet ready for public release
### Exposing Private Products to Advertisers
To expose private products to specific advertisers, you need to set permissions in the **Private Product Access**.
1. Click **Management > Operations** in the left menu.
2. Add advertisers in the **Private Product Access** section.
3. Select the required **Access Permission** and click **Save Changes**.
Only advertisers with private product access permission can see those products.
***
## Payment Method Management
Manage payment methods available to advertisers when booking campaigns.
### Accessing Settings
1. Click **Management > Operations** in the left menu.
2. Configure in the **Payment Method Management** section.
### Card Payment
Adrop collects ad fees via card payment before campaign booking, then settles revenue minus fees monthly. Supports automatic settlement and tax invoice issuance upon campaign completion.
| Card Type | Payment Processor | Fee |
| - | - | - |
| **Domestic cards** | Toss Payments | 3.2% |
| **International cards** | Stripe | 5.5% (additional withholding taxes may apply by country) |
Card payment is recommended. Automatic settlement and tax invoice issuance support reduces operational burden.
### Manual Payment
Publisher settles directly with the advertiser. Adrop provides statements, but publishers must request settlement and issue tax invoices directly.
| Setting | Behavior |
| - | - |
| **Available to All** | All advertisers can select manual payment. |
| **Allowlist Only** | Only advertisers in the **allowlist** can select manual payment. |
| **Not Available** | No advertisers can select manual payment. |
### **Allowlist Management**
1. Select '**Allowlist Only**' for manual payment.
2. Click the **Allowlist** button on the right.
3. Add advertiser emails or domains to allow manual payment and click **Save Changes**.
***
## CPP Budget & Targeting Modification
CPP products are **fixed price** by default. When advertisers book CPP products, the publisher's set price and targeting are applied as-is.
Allowlisted advertisers can directly modify budget and specify targeting when booking CPP campaigns.
### How to Set
1. Click **Management > Operations** in the left menu.
2. Click the **Allowlist** button in **Platform Policy** > **CPP Budget & Targeting Modification** section.
3. Add advertiser emails or domains to allow and click **Save Changes**.
### Use Cases
* Providing CPP products at custom prices to specific VIP advertisers
* Allowing advertisers to directly select their desired targeting
***
## Creative Display Day & Time Settings
Enabling this feature allows setting creatives to display only on specific days or times when registering creatives.
### How to Set
1. Click **Management > Operations** in the left menu.
2. Enable **Creative Display Day & Time Settings** in the **Platform Policy** section.
### How to Use
1. Register a creative in the campaign details.
2. Enable **Creative Display Day & Time Settings** in the **Advanced Settings** section.
3. Select desired days and times.
### Use Cases
* Show food ads only during lunch hours
* Show specific promotional creatives only on weekends
* Concentrate delivery during commute hours
***
## In-House Ad Products
Ad products that the publisher registers directly to promote its own service or content.
### Characteristics
* **No advertiser payment or revenue settlement**: Since the publisher registers these ads directly, no advertiser payment or revenue settlement occurs.
* **Private products only**: Can only be created as private products that are not exposed to advertisers.
* **Subject to Ad.Control fees**: When in-house ads are actually served, their impressions are subject to [Ad.Control fees](/settlement/subscription).
### Delivery Priority
In-house ads are served as a lower priority only when the ad slot would otherwise be empty because no reserved ads (CPM · CPC · CPP) are available. If a reserved ad exists, it is served first. In-house ads are served before [bidding-based backfill ads](/ad-unit/backfill).
**Ad selection order**
1. **Reserved ads** (CPM · CPC · CPP)
2. **In-house ads** — when there are no reserved ads
3. **Bidding-based backfill ads** — when there are neither reserved nor in-house ads (only for ad units that have opted in to backfill)
If none of the above ads are available, no ad is displayed in that slot.
### How to Set
Select the **In-House** billing type in the **Product Info** section when creating an ad product.
***
## Troubleshooting
### Ad Products Needing Attention
If a warning icon appears in the ad product list, verification is needed.
#### Contains Units Needing Modification
The format, size, or options of units included in the creative set have been changed and no longer match the set criteria.
**Symptoms**
* Warning icon displayed in ad product list
* New campaign bookings not possible
**Solution**
1. Remove the affected unit from the set in the ad product edit screen.
2. Or click the 'Auto Configure' button in the top warning bar and move units to appropriate positions if needed, then save the product.
#### Not Transitioning from Preparing to On Sale
Ad requests must occur at least once from all units included in the creative set for On Sale status.
**Solution**
1. Verify the SDK is properly integrated into the app.
2. Display the screen containing the ad unit at least once.
***
## Next Steps
Configure targeting to apply to ad products
***
## FAQ
It's set per ad product. The same setting applies to all campaigns booked with that product.
Ads won't be displayed. Unless you don't want impressions, set a value of 1 or higher.
Device-based. If the same user uses multiple devices, each device is counted separately.
Yes, disable the private setting in ad product edit.
Check if the advertiser has been granted access by clicking the product edit button on the right side of the allowlist. Verify if "Access to All Products" or "Access to Some Products Only" is selected.
The publisher pays. The amount settled to publishers is the amount paid by advertisers minus card fees.
Yes, they're included. However, Adrop doesn't handle settlement for manual payments, so publishers must request settlement directly from advertisers.
Yes, it automatically applies to all accounts with that domain.
Create and operate an in-house ad product for your internal marketing team. You can set impression limits at the campaign booking stage, and if there's no limit, ads will be served on all remaining inventory.
Book the same CPM, CPC, or CPP products you're selling for paid ads. Ads are delivered based on billing type as follows, and bookable budget is also deducted to prevent overbooking.
* CPM: Delivered with logic to achieve target impressions
* CPC: Delivered with logic to achieve target clicks
* CPP: Delivered throughout the campaign period
Turn on the private product toggle to make it private regardless of billing type. After setting the product to private, go to Management > Operations > Private Product Access and grant access to specific domains only.
# Creating Ad Products
Source: https://docs.adrop.io/ad-product/create
Detailed guide to each setting when creating ad products.
## Accessing Create Screen
1. Click **Ad Products** in the left menu.
2. Click the **+ Create Product** button.
***
## Step 1: Enter Product Info
### Product Info
| Item | Description | Required |
| - | - | :-: |
| Ad Product Name | Product name displayed to advertisers | O |
| Billing Type | Select from CPM, CPC, CPP, In-House | O |
| Accept Video Creatives | Whether advertisers can register video creatives for this product. This is a product option, not a billing type. | - |
| Private | Not exposed to advertisers, publisher-only use | - |
#### Ad Product Naming Guide
Write ad product names to **correspond 1:1 with your product brochure**. Advertisers review products in the brochure then book campaigns in the console. Matching names in the brochure and console helps advertisers quickly find and book desired products.
* If brochure has "Home Banner CPM", create the same name in console
* Write so placement and billing type are clearly apparent
* Examples: `Home Banner CPM`, `Feed Native CPC`, `Splash CPP`
#### Billing Types
| Type | Description | Best For |
| - | - | - |
| **CPM** | Charged per 1,000 impressions | Guaranteed impression sales, stabilize revenue with backfill ads for remaining inventory |
| **CPC** | Charged per click | Guaranteed click sales, stabilize revenue with backfill ads for remaining inventory |
| **CPP** | Charged per time period | Premium placements, limited slot sales |
| **In-House** | No advertiser payment or revenue settlement | Publisher promoting its own service or content. Served as a lower priority when no reserved ads are available. See [Advanced Settings — In-House Ad Products](/ad-product/advanced#in-house-ad-products) |
#### Accept Video Creatives
Sets whether advertisers can register video creatives for this product. This is a product option, not a billing type.
For products that accept video creatives, advertisers can use both image/text creatives and video creatives, and a single price set on the product applies regardless of which creative type is used. Prices do not automatically vary by creative type.
Video creative impressions incur a higher Ad.Control fee than image/text creative impressions. Take this into account when pricing products that accept video creatives. See [Subscription and Billing](/settlement/subscription) for the applicable rates.
#### Selling Image and Video Creatives at Different Prices
You cannot set different prices per creative type within a single product. In this case, publishers can operate as follows:
1. Create two products. One product does not accept video creatives, and the other product accepts video creatives.
2. Register the same ad unit in both products. A single ad unit can be included in multiple ad products.
3. Set the desired price for each product, and indicate the distinction and price in the product name so advertisers can distinguish and select between the two products on the booking screen. (e.g., `Home Banner — Image`, `Home Banner — Video`)
### Sales Info
| Item | Description |
| - | - |
| **Currency** | Payment currency (KRW, JPY, INR, USD) |
| **Time Zone** | Time zone for campaign start/end |
| **Unit Price** | Product price |
| **Product Discount Rate** | Product discount rate (%) |
Setting currency and time zone allows selling ad products to international advertisers.
* South Korea (KRW, Asia/Seoul)
* Japan (JPY, Asia/Tokyo)
* India (INR, Asia/Kolkata)
* United States (USD, America/New\_York)
Contact us if you need additional currencies. ([Contact Us](https://adrop.io/contactus))
#### CPM Additional Items
| Item | Description |
| - | - |
| **Minimum Budget** | Campaign minimum budget |
#### CPC Additional Items
| Item | Description |
| - | - |
| **Minimum Budget** | Campaign minimum budget |
#### CPP Additional Items
| Item | Description |
| - | - |
| **Minimum Period** | Campaign minimum period (1-150 days) |
| **Slots** | Number of ads that can be sold simultaneously (1-50) |
The number of ads that can display simultaneously on the same placement. 1 slot means one advertiser has exclusive display, 2+ slots means multiple advertisers' ads rotate via carousel or random distribution.
### Sales Metrics
Used to show expected performance to advertisers and optimize campaign distribution.
| Item | Description |
| - | - |
| **Daily Impressions** | Enter the maximum impressions sellable per day for this ad product. |
| **Expected CTR** | Enter the average click-through rate for this ad product. (Clicks ÷ Impressions × 100) |
* For slot-based products, enter daily impressions based on 1 slot.
* We recommend entering daily impressions and expected CTR conservatively. When advertisers book the maximum budget, underspending may occur or efficiency may be lower than expected.
### Additional Info
| Item | Description |
| - | - |
| **Creative Guide Link** | (Optional) URL to creative specification guide for advertisers. Connect a document with image sizes, file formats, text guidelines, etc. so advertisers can quickly prepare creatives. |
### Advanced Settings
| Item | Description |
| - | - |
| **Frequency Cap** | Maximum times campaigns booked with this product can be shown to a single user per day. (Default: 50) |
Seeing the same ad too often causes user fatigue and decreases CTR. However, too few impressions reduce ad effectiveness. For branding ads, set frequency high; for conversion ads, limit appropriately.
### Selecting Ad Units
Select ad units to include in this product from the unit list on the right.
* At least 1 unit must be selected.
* Separate registration by platform (Android/iOS) is recommended.
Generally, including one ad unit in only one ad product is recommended. If the same unit is included in multiple products, inventory calculation becomes complex, and competition between campaigns may cause unexpected impression results. For CPP products, this may lead to accepting bookings exceeding slot capacity.
**Exception**: When splitting a product by whether it accepts video creatives, the same unit can be registered in both products.
Click the **Next** button.
***
## Step 2: Configure Creative Sets
Configure selected units into sets. Creative sets are the placement units where advertisers upload creatives, and set names are displayed to advertisers, so use names that clearly distinguish placements.
### What are Creative Sets?
Creative sets are **groupings of placements to receive creatives from advertisers**. Units in the same set share identical creatives, so advertisers don't need to register creatives separately for each unit.
For example, if you group Android Home Banner and iOS Home Banner of the same size in the same set, advertisers register one creative to display ads on both platforms.
### Auto Configuration
Units with the same format, size, and options (template, watermark, carousel, etc.) are automatically grouped into one set.
| Criteria | Description |
| - | - |
| Format | Ad type like banner, interstitial, native |
| Size | Ad dimensions (e.g., 320x50, 300x250) |
| Video support | Whether Accept Video Creatives is enabled |
| Video controller | Video controller on/off setting |
| Video skip delay | Video skip delay value |
| Template | Template usage |
| Watermark | Watermark support |
| Carousel | Carousel support |
Even with the same format and size, **if detailed options differ, units will be separated into different creative sets**.
### Creative Set Settings
Configure names and unit placement for each creative set. Creative sets are the units advertisers register creatives to.
| Item | Description |
| - | - |
| **Creative Set Name** | Name displayed to advertisers (e.g., Home Banner, Feed Banner) |
| **Creative Set Configuration** | Drag and drop to move units between sets within the same format, size, and options |
Units in the same creative set share identical creatives. When advertisers register one creative, it displays on all units in the set.
### Unified vs Separate Management
Even for units with the same size/format, you can separate sets to receive different creatives.
| Method | Description | Best For |
| - | - | - |
| **Unified** | Multiple units in one set | Displaying one creative across multiple placements, advertiser convenience |
| **Separate** | Separate set per unit | Wanting different creatives per placement considering user fatigue and UX |
### Cautions
You cannot save the ad product if any units don't have assigned creative sets.
If you modify the format, size, or options (template, watermark, etc.) of units included in an ad product, they won't match set criteria and enter **Needs Modification** status.
Ad products containing units needing modification:
* Cannot accept new campaign bookings
* Existing campaigns continue operating normally
* Warning icon displays in ad product list
**Solution**: In the ad product edit screen, click the 'Auto Configure' button in the top warning bar and move units to appropriate positions if needed, then save the product.
***
## Step 3: Configure Targeting to Display on Product
### Step-by-Step Guide
1. **Select Audience or Context**
Only one type of targeting can be displayed on a single product: either audience or contextual targeting. If you switch tabs, the targeting you selected in the other tab is cleared.
2. **Set targeting to display**
1. Click the switch on the right side of the targeting.
2. Check the targeting options to display on the product.
3. Decide whether the option is required.
| Item | Description | Note |
| - | - | - |
| Optional | This targeting option is provided as a choice when booking ads. Advertisers can choose to select or not select as needed. | For CPP products, the choice is only shown to advertisers when the 'Allow CPP budget modification and targeting specification' option is enabled in Management - Operations - Advanced Features. |
| Include | This targeting option is required and applied when booking ads. Advertisers cannot deselect this option. | |
| Exclude | This targeting option is required to be excluded when booking ads. Advertisers cannot deselect this option. | |
4. Set premium rate
Set the premium rate to apply when this targeting is selected. (0% \~ 200%)
Example: With 20% setting, base price $10 → $12
5. Click Save button to save.
Only targeting options that are Available or In Use can be displayed on products.
CPP / In-house products cannot set targeting premium rates.
If a context targeting has options set to Include or Exclude, no other context targeting can be displayed on the same product.
### Estimated Target Size
You can check the estimated target size based on the configured targeting. It is shown only for audience targeting. Only ratios are shown to advertisers instead of specific numbers.
Estimated target size is the estimated maximum reach based on configured targeting conditions, and actual impressions/reach may differ.
***
## Step 4: Create Product
Click **Create Product** to complete creation.
Created products can be viewed in the **Ad Products** list.
| Status | Description | Campaign Booking |
| - | - | :-: |
| **Preparing** | Some units in the creative set have not yet completed SDK integration | ✅ |
| **On Sale** | All units have completed SDK integration | ✅ |
| **Deleting** | Waiting to be completely deleted after deletion request | ❌ |
Ad requests must occur at least once from all units included in the creative set. Display the ad unit screen at least once after integrating the SDK into your app to automatically transition to On Sale status.
***
## Next Steps
Learn how to edit and delete created products
***
## FAQ
Choose CPM for impression guarantees, CPC if you have many conversion-focused advertisers, CPP to sell exclusive exposure on premium placements.
Generally not recommended. Including the same unit in multiple products complicates inventory calculation, and for CPP products, may lead to accepting bookings exceeding slot capacity.
However, when selling video creatives at a different price from image/text creatives, registering the same unit in both a video-allowing product and a video-disallowing product is a valid use case.
These names appear when advertisers upload creatives. Use names that clearly distinguish placements like "Home Banner", "Feed Middle".
Even with the same format and size, **if detailed options (video support, video controller, video skip delay, template, watermark, carousel, etc.) differ**, units are separated into different creative sets. Hover over the top of separated formats to see which settings caused the separation. You can combine them by matching the option settings of the units.
Enter maximum daily sellable impressions and average CTR. Start conservative initially and adjust as data accumulates.
Yes, setting currency and time zone in Sales Info allows selling ad products to international advertisers. Currently supports KRW(₩), JPY(¥), INR(₹), and USD(\$), and all time zones are supported. Contact us if you need additional currency support. ([Contact Us](https://adrop.io/contactus))
# Managing Ad Products
Source: https://docs.adrop.io/ad-product/manage
Learn how to edit or delete created ad products.
***
## Ad Product Status
You can check the current status of each product in the ad product list. Campaign booking availability depends on the status.
| Status | Description | Campaign Booking |
| - | - | :-: |
| **Preparing** | Some units in the creative set have not yet completed SDK integration | ✅ |
| **On Sale** | All units have completed SDK integration | ✅ |
| **Deleting** | Waiting to be completely deleted after deletion request | ❌ |
Ad requests must occur at least once from all units included in the creative set. Display the ad unit screen at least once after integrating the SDK into your app to automatically transition to On Sale status.
***
## Editing
### Edit Procedure
1. Select the product to edit from the **Ad Products** menu.
2. Click the **\[...]** button in the top right of the detail page.
3. Select **Edit**.
4. Make changes and click **Create Product**.
### Editable Items
| Item | Description |
| - | - |
| **Ad Product Name** | Product name displayed to advertisers |
| **Billing Type** | CPM, CPC, CPP |
| **Sales Info** | Unit price, product discount rate, minimum execution amount/period |
| **Sales Metrics** | Daily impressions, expected CTR |
| **Targeting Settings** | Targeting categories, required selection settings |
| **Advanced Settings** | Frequency cap, video skip delay |
| **Creative Set Configuration** | Add/separate/remove units, rename sets, change set composition |
You can change the product discount rate for promotions or seasonal events. Enter the discount rate (%) in the Sales Info section of the edit screen. Changed discount rates apply to newly booked campaigns.
### Cautions
If the creative set contains units in "needs modification" status, you cannot edit the ad product. First remove those units from the set or revert the unit settings.
If the creative set contains units in "needs modification" status, you cannot edit the ad product. First remove those units from the set or revert the unit settings.
Changes apply to new campaigns only. Live campaigns continue operating with their original booking settings.
***
## Deleting
### Delete Procedure
1. Select the product to delete from the **Ad Products** menu.
2. Click the **\[...]** button in the top right of the detail page.
3. Select **Delete Product**.
4. Enter the product name to delete and click the **Delete** button.
### Cautions
Deletion requires **Owner** permission or higher.
Products are not deleted immediately after request—they enter **Deleting** status.
* If no live campaigns: completely deleted 3 days after request
* If live campaigns exist: completely deleted 3 days after the last campaign ends
New campaign bookings and product edits are not possible while in Deleting status.
### Canceling Deletion
Products in Deleting status can be restored before complete deletion.
1. Select the product in Deleting status from the **Ad Products** menu.
2. Click the **\[...]** button in the top right of the detail page.
3. Select **Cancel Deletion**.
When deletion is canceled, the product immediately restores to its previous status (On Sale or Preparing).
Completely deleted ad products cannot be recovered. Cancellation is only possible during the deletion waiting period.
***
## List Management
### Changing Display Order
You can change the order of ad products displayed to advertisers.
1. Click the **Change Order** button in the **Ad Products** menu.
2. Drag and drop to adjust product order.
3. Click **Save Changes**. (Click **Cancel** to discard changes)
Placing popular or recommended products at the top can guide advertiser choices.
### Filtering Lists
Filter the ad product list by various conditions to quickly find desired products.
* **Filter by status**: Preparing, On Sale, Deleting
* **Filter by billing type**: CPM, CPC, CPP
### Checking Targeting Info
In the **Targeting** section of the product detail page, you can check:
* Estimated target size
* Targeting options to display on the product
* Required selection targeting options
***
## Next Steps
Verify ad products work correctly with test campaigns
***
## FAQ
No. All product settings including targeting and surcharge rates are applied as a snapshot at the time of campaign booking (or modification). Live campaigns continue operating with their original booking settings.
Yes. after deletion completes, you can create a new ad product with the same name.
Ad requests must occur at least once from all units included in the creative set. Integrate the SDK into your app and display the screen containing the ad unit at least once.
# What are Ad Products?
Source: https://docs.adrop.io/ad-product/overview
Ad Products are bundles of Ad Units sold to advertisers. Well-designed Ad Products significantly help attract advertisers and maximize revenue.
***
## What is an Ad Product?
An **Ad Product** is simply an **advertising package sold to advertisers**.
It combines one or more Ad Units with pricing and conditions set, allowing advertisers to purchase the Ad Product and book campaigns.
| Category | Description | Example |
| - | - | - |
| **Ad Unit** | Individual placement where ads are displayed | Home top banner, feed native |
| **Ad Product** | Unit bundled for sale | "Home Banner CPM", "Premium Package" |
***
## Components of an Ad Product
### Pricing Policy
Set the billing method and unit price for advertising.
| Billing Method | Description | Features |
| - | - | - |
| **CPM** | Billing per 1,000 impressions | Stable revenue, suitable for branding ads |
| **CPC** | Billing per click | Performance ads, conversion-focused |
| **CPP** | Period + slot-based billing | Premium placements, guaranteed exclusive display |
### Targeting
Specify required targeting for the Ad Product or set targeting options that advertisers can choose.
### Creative Set
A bundle of units where ads will be displayed. When units with the same format and size are grouped into a creative set, they can share the same creative, so advertisers don't need to register creatives separately for each unit.
Example: Android Home Banner + iOS Home Banner = "Home Banner" creative set
***
## Why is Ad Product Design Important?
Even with the same Ad Units, revenue varies depending on how they are productized.
### Design with Your Ad Media Kit
Ad Products should correspond 1:1 with your **Ad Media Kit** delivered to advertisers. If your media kit includes "Home Banner CPM", create a product with the same name and conditions in the Console.
A template you can use when creating your Ad Media Kit.
When you upload an Ad Media Kit for AdNote, Adrop's AI agent learns the content and automatically recommends suitable products to advertisers.
***
## Ad Product Strategy Guide
### 1. Create an Ad Media Kit
The media kit delivered to advertisers should clearly communicate your platform's value and maximize the appeal of your Ad Products.
#### Value Proposition
* **Prove influence with objective metrics**: Present specific numbers such as MAU, cumulative content, and market share.
* **Define differentiated moments**: Emphasize effective 'moments' for advertising, not just traffic. (e.g., question pages with clear search intent)
#### Product Portfolio
* **Comparison table required**: Organize product name, placement, unit price, and expected click rate in one table.
* **Clear distinction by placement**: Specify exposure locations such as "Home top", "Feed middle", "Detail page bottom".
* **Highlight premium products**: Emphasize the appeal of premium products such as top positions and high expected click rates.
#### Targeting Options
* **Provide specific targeting examples**: Specify available targeting such as "Finance/Insurance", "Parenting/Family".
* **Specify premium rates**: Inform advertisers in advance of price changes when applying targeting (e.g., 10% premium).
#### Creative Guidelines
* **Organize specifications by product**: Organize image sizes and text character limits in a table.
* **Specify required items**: Guide required elements such as advertiser logo, body text, and button text.
#### Use Cases
* **Include success stories**: Presenting actual ad campaign cases and performance increases persuasiveness.
### 2. Create Products in Ad Control
Accurately implement what was promised in the media kit into the system.
#### Product Mapping
* **1:1 matching**: Set the product name in the Console to match the media kit.
* **Unified billing method**: Apply the billing method specified in the media kit (CPM, CPC, etc.) as is.
#### Placement Settings
* **Specific exposure locations**: Define detailed positions like "Question Page - Top of Answer".
* **Separate app/web management**: Separate app-only placements (popups, etc.) from web placements to track performance.
#### Targeting Settings
* **Implement targeting**: Register the targeting options specified in the media kit in the system.
* **Auto-apply premium**: Set premium rates to automatically apply when targeting is selected.
#### Campaign Conditions
* **Minimum campaign amount**: Set a minimum campaign amount to maintain average order value.
***
## Table of Contents
1. ⚡ [Quickstart: Create Your First Ad Product](/ad-product/quickstart)
2. [Creating Ad Products](/ad-product/create)
3. [Managing Ad Products](/ad-product/manage)
4. [Testing Ad Products](/ad-product/testing)
5. [Promoting Ad Products](/ad-product/promotion)
6. [Advanced Settings](/ad-product/advanced)
# Promoting Ad Products
Source: https://docs.adrop.io/ad-product/promotion
Set up your ad profile and promote ad products to attract advertisers.
Enter your Ad Note product brochure and promotion info to promote your ad business on the Ad Note community.
***
## Registering Product Brochure
Register a brochure file to provide to advertisers. Upload in PDF format.
### Accessing Settings
1. Click **Project Settings** in the left menu.
2. Select the **Ad Profile** tab.
3. Upload your brochure in the **Ad Product Introduction** section.
### Brochure Types
| Item | Description |
| - | - |
| **Ad Platform Brochure** | Brochure that advertisers can download from the ad platform page. Include ad product lineup, pricing, expected performance, creative guidelines, etc. |
| **Ad Note Brochure** | Brochure displayed in the Ad Note community product list. Adrop's AI agent analyzes the brochure content to automatically recommend suitable products to advertisers. Include detailed product features, target audiences, and performance data for more accurate recommendations. |
### Ad Note Brochure Guidelines
| Item | Description |
| - | - |
| **Contact Info** | Please use only `contact@adrop.io` for contact information. |
| **Product Names** | Ensure brochure product names match console product names. |
| **Confidential Info** | Exclude information that cannot be shared externally. |
| **Policies** | Exclude individual publisher policies that differ from official policies, and publisher-specific booking processes. |
***
## Registering Promotions
Register promotional info like discounts and free trials to attract advertiser interest. Registered promotions are posted on the Ad Note community.
### Accessing Settings
1. Click **Management Menu** in the left menu.
2. Select the **Promotions** tab.
### Promotion Types
| Type | Description | Examples |
| - | - | - |
| **Discount** | Discounts on ad spend. Effective for acquiring new advertisers or encouraging repeat purchases. | "30% off first campaign", "20% off for spending over \$10,000" |
| **Free Trial** | Allow free ad execution. Guide advertisers to paid conversion after they directly verify performance. | "1 week free trial", "100K free impressions" |
| **Bonus** | Provide additional impressions, extended periods, etc. Effective for increasing average order value. | "10% bonus impressions on \$10K spend", "Buy 2 weeks, get 1 week free" |
### Promotion Info Fields
| **Field** | **Description** |
| - | - |
| **Title** | Brief explanation of the promotion's key points. Write so advertisers can quickly understand. (max 50 characters) |
| **Details** | Describe eligibility conditions, usage methods, notes, etc. in detail. Include all information advertisers need to receive the promotion. (max 1000 characters) |
| **End Date** | Promotion validity period. Displays as expired promotion when end date passes. |
### Managing Promotions
* **Publish**: Make the promotion visible to advertisers. Only published promotions are displayed in the Ad Note community.
* **Unpublish**: Temporarily hide the promotion. Use for promotions that need editing or aren't ready for release.
* **Delete**: Permanently delete the promotion. Deleted promotions cannot be recovered.
Select discount, free trial, or bonus templates when creating promotions for quick setup.
***
## Next Steps
Review advanced features like frequency cap and private products.
***
## FAQ
It's a brochure displayed in the Ad Note community product list. Adrop's AI agent learns the brochure content to automatically recommend suitable products to advertisers.
Registered promotions are posted on the Ad Note community. Advertisers can view promotions and receive them when booking campaigns.
# Quickstart: Create Your First Ad Product
Source: https://docs.adrop.io/ad-product/quickstart
Step-by-step guide to quickly create your first Ad Product.
> ⏱️ Estimated time: About 5 minutes
## Before You Start
* At least 1 Ad Unit created
* Product name to sell (e.g., "Home Banner CPM", "Feed Native")
If you don't have an Ad Unit, complete this quickstart first.
Step-by-step guide to creating your first Ad Unit.
## Create an Ad Product
Log in to [Ad Control Console](https://console.adrop.io).
1. Click **Ad Products** in the left menu.
2. Click the **+ Create Product** button.
***
## Step-by-Step Guide
**Product Information**
1. Enter the **Ad Product Name**. (e.g., `Home Banner CPM`, `Feed Native CPC`)
2. Select the **Billing Type**.
* **CPM**: Billing per 1,000 impressions
* **CPC**: Billing per click
* **CPP**: Period-based billing
**Sales Information**
Enter the **Unit Price**.
* For CPP, additionally set **Minimum Campaign Period** and **Number of Slots**.
**Sales Base Metrics**
For CPM/CPC, enter **Daily Expected Impressions** and **Expected Click Rate**.
Direct input is required for initial setup, but automatic trend prediction will be provided once ad data accumulates. (To be provided as Inventory Insights addon in the future)
**Select Ad Units**
From the unit list on the right, select the **Ad Units** to include in this product.
Click the **Next** button.
Selected units are automatically organized into sets.
1. Units with the same format/size/options are grouped into one set.
2. Drag and drop units to move them to different creative sets if needed.
3. Name each creative set. (e.g., `Home Banner`, `Feed Banner`)
Set up targeting options that advertisers can use when booking campaigns.
1. Select either **Audience** or **Contextual** targeting.
2. Turn on the switch for targeting options to expose.
3. Select how each option is applied:
* **Select**: Advertiser can optionally choose
* **Include**: Mandatory inclusion
* **Exclude**: Mandatory exclusion
4. Set the **surcharge rate** (0%–200%).
Click the **Create Product** button to complete.
***
## Complete
The created Ad Product can be selected by advertisers when booking campaigns.
Check that the product status is **On Sale**.
## Next Steps
**Learn More**
Check each configuration item in detail.
**Next Quickstart**
Step-by-step guide to booking an ad campaign.
***
## FAQ
An Ad Unit is an individual placement where ads are displayed, while an Ad Product is a bundled unit sold to advertisers. Advertisers purchase products, and ads are displayed on the units included in that product.
It's best to create them to correspond 1:1 with your Ad Media Kit. If your media kit has "Home Banner CPM", create a product with the same name and conditions in the Console.
When a product becomes On Sale, advertisers can book campaigns. Actual ad display starts after advertisers book campaigns and they are approved.
# Testing Ad Products
Source: https://docs.adrop.io/ad-product/testing
Run test campaigns in the Developer Center to verify Ad Products work normally.
## Before You Start
* Ad Unit SDK integration complete (requires developer collaboration)
* Ad Product to test created
***
## What is a Test Campaign?
A method to display test ads only from specific IP addresses. Allows quick technical verification without affecting actual users.
***
## Testing Procedure
1. Click **Developer Center** in the left menu.
2. Select the Ad Product to test in the **Test Campaign** section.
3. Check the IP address in the **Test Request** step and click the **Test Request** button.
4. Run the app from that IP and verify ads display normally.
5. Click the **End Test** button when testing is complete.
IP address is auto-detected, but can be manually entered if needed.
Test campaigns are valid for **30 minutes only**.
### To Test Again?
Click **End Test**, then press the **Reset Checklist** button at the bottom left to initialize.
***
## Test Status
| Status | Description |
| - | - |
| **Incomplete** | Haven't started testing yet |
| **Testing** | In progress after test request |
| **Test Complete** | Testing ended |
***
## Next Steps
Check how to set up ad profiles and attract advertisers
***
## FAQ
No, test campaigns only display ads from the specified IP address. They don't affect actual users.
Return to the test request step and request a test again. Tests are valid for 30 minutes.
# Maximize Revenue with Backfill
Source: https://docs.adrop.io/ad-unit/backfill
Adrop is an all-in-one monetization solution that allows you to operate from direct deal ads to network ads in one place, maximizing ad revenue for app developers.
## What is Backfill?
Backfill is a feature that automatically connects multiple ad sources to fill gaps when there are no high-priced direct deal ad bookings, ensuring continuous ad delivery.
Combined with Adrop's unique bidding and waterfall optimization technology, it simultaneously increases ad pricing (eCPM) and Fill Rate while providing more stable revenue growth.
* Automatically connects multiple global ad sources
* Optimizes pricing through Adrop's unique bidding and waterfall optimization strategy
* Secures optimal pricing (eCPM) and Fill Rate by country, placement, and time zone
* Applies after periodic performance A/B testing
The key is to continuously increase overall eCPM while growing direct ad proportion. Ad Control allows you to manage this process on a single platform.
***
## Why Do You Need Backfill?
### Direct Ads Alone Have Limitations
Direct deals have high pricing but struggle to fill all inventory.
### Network Ads Alone Are Also Insufficient
Network ads like AdMob and AppLovin fill empty inventory, but pricing has limits no matter how optimized. Competition among network ads alone cannot dramatically increase eCPM.
### True Monetization Is Their Combination
| Ad Type | Advantages | Disadvantages |
| - | - | - |
| **Direct Ads** | High pricing | Fill Rate limitations |
| **Network Ads** | High Fill Rate | Pricing limitations |
| **Direct + Backfill** | **High Pricing + High Fill Rate** | - |
**Ad Control's Backfill** automatically fills inventory without direct ads with network ads. You can simultaneously secure the high profitability of direct ads and the stability of network ads.
***
## How It Works
An ad request occurs from the app.
Checks if there are booked direct ads.
**Display Direct Ads**
Maximize revenue with high pricing
**Display Backfill Ads**
Stable monetization even for empty inventory
Direct ads display first, and Backfill ads only display when there are no direct ads. You can monetize even empty inventory while protecting direct ad revenue.
***
## Delivery Priority
Backfill ads are served only when neither reserved ads nor in-house ads are available. This applies only to ad units that have opted in to backfill.
**Ad selection order**
1. **Reserved ads** (CPM · CPC · CPP)
2. **[In-house ads](/ad-product/advanced#in-house-ad-products)** — when there are no reserved ads
3. **Bidding-based backfill ads** — when there are neither reserved nor in-house ads
***
## Activating
### Start Ad Monetization
In the Ad Unit menu, click the 'Turn on Backfill Ads' button for the unit you want to start ad monetization.
If you're not using the latest SDK, you'll need help from a developer to update for Backfill feature usage.
[Setup Guide →](https://docs.adrop.io/sdk/overview#supported-ad-formats)
This is a method to certify that your ad placement is safe inventory, and needs to be added only once per app. If you've already added ads.txt, there's nothing additional to do.
[Setup Guide →](https://docs.adrop.io/sdk/ads_txt)
Once you've completed all the above steps, our team will begin review and the Backfill ad status in the console will change to 'Monetizing' within 2 business days. You can check revenue in the Reports menu 6 hours after ad monetization starts.
Upon first activation request, a representative will contact you for optimized operation of direct and Backfill ads.
## Pause Ad Monetization
Backfill ads can be paused and restarted at any time.
* Status changes are reflected within **maximum 30 minutes**.
* Does not affect direct ad campaigns.
***
## Turn Off Ad Monetization
You can completely delete Backfill integration info and turn off Backfill ads.
Turning off Backfill ads will delete integration info. Turning it back on later requires re-setup of integration info, so ads will not be served immediately.
***
## Supported Ad Types
Check the supported ad units by format and development environment for ad units created in the console.
| Ad Format | Native App | Web (WebView Mobile App) | Notes |
| - | - | - | - |
| **Banner** | Y | Y | |
| **Interstitial** | Y | N | |
| **Rewarded Video** | Y | N | |
| **Native** | Y | Y | For web environments, supported as full image banner format fitted to container area. |
| **Popup** | N | N | If you want Backfill for popups, we recommend implementing popups through banner ad units. |
| **Splash** | N | N | |
### Native Ad Specifics
* In native app environments, ads are served using ad fields. For optimal monetization, please use all available fields.
* Available ad fields: Advertiser logo, Advertiser name, Call-to-Action (CTA) button, Ad title, Ad description
* **Note**: Some fields may not be displayed depending on the creative assets prepared by the advertiser.
* In web environments, native ad fields are not used, and ads are served as banners fitted to the entire ad area specification.
### WebView Mobile App Environment Specifics
**Site Approval:** Backfill ad monetization is only possible after ad network site approval is completed. Review may be rejected in the following cases:
* When the top-level domain has insufficient content
* When sitemap is not configured
***
## Ad Quality Management
Since Backfill ads are monetized through real-time bidding from various ad networks, pre-screening of creatives is not possible unlike direct ads. Adrop provides safe ad monetization services by automatically blocking sensitive categories and conducting regular/irregular creative quality monitoring and improvement work.
1. Automatic Sensitive Category Blocking
Ads from the following categories are blocked by default.
* Adult content
* Direct sexual references
* Social casino games
* Gambling and betting
2. Creative Quality Monitoring
We continuously manage ad creative quality through regular and irregular monitoring, and periodically strengthen blocking of inappropriate advertisers and creatives based on monitoring results.
***
## Custom Blocking Options
If you have additional categories or advertiser lists you'd like to block, please submit them using the method below.
### Submission Method
Please make a copy of this [template](https://docs.google.com/spreadsheets/d/1NaHro1ng9on_MoKEL9OqxNoU_TWqKcwCZrT4c5Y9_z8/edit?gid=1591726783#gid=1591726783) file.
Please enter \[Company Name] in the file name.
Please create your blocking list.
* General category blocking: Set to Blocked
* Sensitive category blocking: Set to Blocked
* Advertiser blocking: Enter the top-level domain URL of advertisers
Submit the completed file to [contact@adrop.io](mailto:contact@adrop.io).
***
## Checking Backfill Performance and Revenue
You can check ad performance and revenue in the Reports > **Backfill** tab.
| **Metric** | **Description** |
| - | - |
| **Impressions** | Number of times ads were actually displayed on user screens |
| **Clicks** | Number of times users clicked on ads |
| **CTR** | Rate of clicks among ad impressions |
| **CPC** | Ad revenue per 1 click |
| **CPM** | Revenue per 1,000 ad impressions |
| **Revenue** | Final ad revenue after Adrop commission deduction |
| **Show Rate** | Ratio of ads displayed in the app relative to ad requests |
### Report Update Cycle
Backfill reports are automatically updated every 6 hours and may take up to 24 hours to reflect the latest data.
### Segments
Select a segment to break down data by that criteria.
| Segment | Description |
| - | - |
| **Unit** | Compare backfill revenue by unit |
### View by Date
With a segment selected, use the **By Date** checkbox to toggle date breakdown.
* **Checked**: Daily data is expanded under each segment item
* **Unchecked**: Only summary data per segment item is displayed
When no segment is selected, date is the base axis and the checkbox is disabled.
### Export
You can export files via email by clicking CSV Export.
***
## Next Steps
Check how to bundle Ad Units to sell as Ad Products.
***
## FAQ
Direct ads automatically display first, and Backfill only fills inventory without direct ads. No manual switching work required. With other solutions, developers must modify code each time direct ads come in, but Ad Control automatically coordinates direct ads and Backfill within a single Ad Unit.
Not recommended. Unifying with Ad Control Ad Units allows real-time switching between direct ads and network ads to maximize revenue. Mixing multiple solutions consumes unnecessary resources in operations and development, and you can't enjoy the benefits of integrated reporting and optimization. To seriously expand your ad business, managing direct ads and Backfill together on one platform is efficient.
Actually recommended. Start monetization with Backfill first while accumulating traffic data. This data becomes powerful negotiation material during direct ad sales. Once advertisers are secured, just register Ad Products in Console. You can switch to direct ad monetization immediately without separate development.
Network ads have pricing limits because they receive ads displayed simultaneously on multiple apps through auction. In contrast, direct ads can receive premium pricing as advertisers purchase specific apps and placements. Securing base revenue with Backfill while increasing direct ad proportion is the key to revenue maximization.
# Backfill Ad Placement Guide
Source: https://docs.adrop.io/ad-unit/backfill-placement-guide
Placement patterns by ad format that maximize Backfill revenue without hurting user experience.
## Overview
Backfill ads automatically fill inventory when there are no direct ads. Because they aren't pre-screened by the publisher the way direct ads are, **how you surface them** has an outsized effect on click-through rate, completion rate, and user experience.
This page covers recommended placement strategies per ad format and the best practices that maximize revenue without degrading UX.
***
## Strategy at a Glance
| Format | Core Principle | Recommended Placement |
| - | - | - |
| **Rewarded** | Confirm user intent (Opt-in) | When the user explicitly chooses "Earn reward" |
| **Interstitial** | Natural transition points | Between screens/levels/content units |
| **Native** | Blend with content flow | Inserted at intervals in feeds/lists |
| **Banner** | Anchored slot | Persistent top/bottom of content area |
***
## Rewarded Ads: Confirm User Intent
Rewarded Backfill is a value-exchange model where the user voluntarily watches an ad to earn a reward. Confirming user intent before showing the ad raises both click-through rate and completion rate.
### Recommended Patterns
**1. Action button that names the reward**
Make the reward explicit in the button label.
* ✅ "Watch video to earn a life"
* ✅ "Watch ad to get 100 coins"
* ❌ "Watch ad" (reward unclear)
* ❌ "Continue" (doesn't signal it's an ad)
**2. Sync the UI with ad readiness**
Disable or hide the action button when no ad is ready, so users don't tap into an empty response. A failed first attempt rarely leads to a second.
**3. Design natural entry points**
Surface the option at moments where the user actually wants the reward.
* When the player is out of lives/turns in a game
* When the user wants to unlock paid content one time
* For daily check-in or mission rewards
### Patterns to Avoid
* Auto-playing the ad without explicit user selection
* Pressure copy like "skip and lose your reward"
* Vague prompts where the user can't tell what they'll get for watching
The key metric for Rewarded Backfill is **completion rate**, not click-through rate. Clearer user intent means higher completion, which drives up eCPM.
### Allowed Reward Types
Rewards for Rewarded ads must be **indirect or non-monetary items only**. **Direct monetary items — cash, cryptocurrency, gift cards, and similar — are not allowed.**
Examples of allowed rewards:
* **Discounts, loyalty rewards, or points** usable within the publisher's app/site
* **Free shipping** or a **free trial** of a product or service
* In-app items such as an **extra life** or **skin** for a game character
You may offer a reward only when **all** of the following conditions are met:
1. The reward is only redeemable and usable for an item or service **within the publisher's platform, website, or app**
2. The reward is **non-transferable** (for example, real-world goods or points that can be transferred to a third party are not allowed)
3. If the reward is a **discount or voucher for a physical item**, it **does not exceed 25%** of the item's total value
If you offer randomized rewards, disclose the odds to the user **before** the ad is presented. All possible outcomes must be easily accessible to the user, and the chance of receiving a reward must be **greater than 0**.
You must **deliver the promised reward** whenever a user completes the required action (such as finishing the ad). Failing to grant the reward is a policy violation.
***
## Interstitial Ads: Natural Transition Points
Interstitials cover the entire screen, so the most important thing is to show them **at moments that don't interrupt the user's flow**.
### Recommended Moments
* **Game level / stage transitions**: between rounds
* **End of a content unit**: after an article, video, or chapter
* **Screen transitions**: home → detail, results → home, etc., where the user clearly perceives a transition
* **Session resume**: when the user returns after a background period (Splash ads can also fit here)
### Apply Frequency Capping
Showing interstitials too often increases ad fatigue and leads to blocking or uninstalls.
* Maintain at least a **1-3 minute interval** between impressions for the same user
* Consider a **per-session cap** on total impressions
### Patterns to Avoid
* Showing **immediately** after a user action (tap, input) → accidental clicks
* Showing during active gameplay → breaks the flow
* Showing right at app launch → before the user has perceived any value
If an interstitial appears within a very short window after a user tap or click, it can cause invalid traffic from accidental clicks. Leave enough buffer between user actions and ad presentation.
***
## Native Ads: Blend with Content Flow
Native Backfill ads are visually integrated with surrounding content. The balance is to **blend in naturally while still clearly labeling the unit as an ad**.
### Recommended Patterns
* **Insert into feeds/lists at consistent intervals** (e.g., one ad per 5-10 content items)
* Match **typography, corner radius, and spacing** with surrounding content
* Show a clear **"Ad" / "Sponsored" label** to prevent mistaken clicks
* **Reserve container size in advance** to avoid layout shift (CLS) when the ad loads
### Patterns to Avoid
* Blending so deeply that ads are indistinguishable from content (policy risk)
* Stacking ads on the first screen
* Unsized containers that cause content to jump when the ad arrives
***
## Banner Ads: Anchored Slots
Banner Backfill is the lowest-friction format, so a **consistent, anchored placement** is recommended.
### Recommended Patterns
* Anchored top or bottom in the safe area
* Positioned so it never overlaps content
* Adaptive slot that handles both portrait and landscape
### Patterns to Avoid
* Floating banners that cover content
* Close buttons placed right next to the ad's click area → accidental clicks
* Multiple stacked banners on a single screen
***
## Common Principles
### Preload
If the next impression is predictable, preload the ad in advance. Showing a loading delay at impression time breaks the user flow and can cause the impression to be lost entirely.
### Branch UI by Ad State
Control your UI for each state: **ready / load failed / showing / dismissed**. For Rewarded especially, disabling the action button on load failure reduces user confusion.
### Manage Frequency and Caps
Define **minimum interval** and **maximum count** per user/session to manage ad fatigue. Long-term retention beats short-term revenue, even for Backfill economics.
### Prevent Invalid Traffic
* Separate ad zones from action zones clearly
* Validate the layout to prevent auto-clicks or accidental taps
* Leave a time buffer between user action and ad presentation
***
## Format Support
| Ad Format | Native App | Web (WebView Mobile App) |
| - | - | - |
| Banner | Y | Y |
| Interstitial | Y | N |
| Rewarded Video | Y | N |
| Native | Y | Y |
| Popup | N | N |
| Splash | N | N |
For full format support details, see [Maximize Revenue with Backfill](/ad-unit/backfill).
***
## Related
How to enable and operate Backfill
Create the right unit for your placement
# Creating Ad Units
Source: https://docs.adrop.io/ad-unit/create
Ad Units are placement units where ads are actually displayed. You can create multiple Ad Units tailored to various locations and formats within your app.
## Creation Process
Select the app where ads will be displayed. Create a new one if none exists.
Enter a name that distinguishes the placement.
**Naming Tips**
We recommend the format `platform_location_format`.
| Component | Description | Examples |
| - | - | - |
| **Platform** | App platform | `aos`, `ios`, `web` |
| **Location** | Screen where ad is displayed | `home`, `feed`, `detail`, `gameover` |
| **Formats** | Ad Unit format | `banner`, `popup`, `interstitial`, `rewarded` |
**Examples**
* `aos_home_top_banner`
* `ios_feed_native`
* `web_main_popup`
* `aos_gameover_interstitial`
* `ios_stageclear_rewarded`
Choose the Ad Unit format.
* Banner
* Splash
* Popup
* Native
* Interstitial
* Rewarded
Native requires directly configuring layouts to match app UI. For quick integration, we recommend Banner.
Select from provided specifications by format, or enter a custom size directly.
**What is dp?**
dp (density-independent pixel) is a unit that maintains consistent size regardless of screen density. Ad Unit sizes are set in dp units.
Creating creatives at 3x px of the unit size ensures clear display even on high-resolution devices.
Example: 320×100 unit → 960×300px creative
Set options provided for each format.
| Formats | Main Options |
| - | - |
| **[Banner](/ad-unit/formats#banner)** | Carousel, indicator, rolling time |
| **[Splash](/ad-unit/formats#splash)** | Display duration, background color |
| **[Popup](/ad-unit/formats#popup)** | Template, close options, carousel |
| **[Native](/ad-unit/formats#native)** | CTA button, advertiser profile, text items |
| **[Interstitial](/ad-unit/formats#interstitial)** | Close button delay time |
| **[Rewarded](/ad-unit/formats#rewarded)** | Reward callback |
***
## Creation Complete
Once an Ad Unit is created, an **Ad Unit ID** is issued. Provide this ID to developers for SDK integration.
| Item | Description |
| - | - |
| **Ad Unit ID** | Used for ad requests in SDK, cannot be modified |
| **Unit Name** | Name for management, can be changed anytime |
***
## Cautions
* Ad Unit ID cannot be changed after creation. Create a new Ad Unit if changes are needed.
***
## Managing Apps
Registered apps can be managed in the **Settings > App** menu.
* View and modify app information
* Check App ID
* Delete app
### Ownership Verification
Completing app ownership verification displays the app as trustworthy to advertisers. Unverified apps appear as unverified to advertisers, which may be disadvantageous for attracting ads.
**Android**
1. Register contact email for the app in Google Play Store.
2. Click the **Reload Email** button in Console to sync email.
3. Click the **Verify** button to send a verification code to that email.
4. Enter the received verification code to complete verification.
**Web**
1. Copy the TXT record provided by Console.
2. Add the TXT record to the domain's DNS settings.
3. DNS propagation may take up to 48 hours.
4. Click the **Verify** button to validate immediately.
**iOS**
iOS apps currently do not offer ownership verification functionality.
***
## Next Steps
Check how to modify and delete created units.
***
## FAQ
Yes, Android and iOS apps are registered separately, so you need to create units for each app separately. Even for the same 'home banner' location, units are separated by platform so you can compare and analyze performance in reports.
Deployed apps may already be requesting ads with the existing unit ID. Changing the ID means ads won't display to users who haven't updated, and report data continuity will break. If changes are needed, create a new unit and transition through app updates.
Yes, you can. Apps in development can be registered by entering just the package name (Android), bundle ID (iOS), or domain (Web). However, ownership verification is only possible after store release.
# Supporting Dark Mode
Source: https://docs.adrop.io/ad-unit/dark-mode
Guide on how to provide ad creatives that match your app's Dark Mode.
## What is Dark Mode?
Dark Mode is a UI theme that uses bright text on a dark background. When the app is in Dark Mode, displaying ads with dark-toned creatives improves user experience.
Enabling the Dark Mode creative feature allows you to operate ads optimized for both light mode and Dark Mode environments with a single Ad Unit.
***
## Supported Formats
The following Ad Units support Dark Mode creatives.
* Banner
* Popup
* Splash
* Native
Video creatives do not support Dark Mode.
***
## Configuration Method
Enable the **Receive Dark Mode Creatives** toggle when creating or modifying an Ad Unit.
| Setting | Behavior |
| - | - |
| **ON** | Can receive Dark Mode creatives |
| **OFF** | Always receive only default (light) creatives |
***
## How It Works
1. **Ad Unit Setting**: Enable receiving Dark Mode creatives in the Ad Unit.
2. **Creative Registration**: Advertisers register both light mode and Dark Mode creatives when booking campaigns.
3. **SDK Request**: The SDK automatically detects the app's current theme (light/dark) and makes requests.
4. **Creative Response**: Returns Dark Mode creative when Dark Mode is requested.
### Fallback Behavior
If no Dark Mode creative is registered, light mode creative is displayed.
***
## Cautions
**Changing Settings During Campaign**
If you enable Dark Mode for an Ad Unit included in a running campaign, light mode creative is displayed as the default.
**Creative Review**
Light mode and Dark Mode creatives are reviewed simultaneously. If either violates policy, the entire creative set is rejected.
***
## Next Steps
Create your own Ad Unit
# Ad Unit Formats
Source: https://docs.adrop.io/ad-unit/formats
Introducing the 6 Ad Unit formats supported by Ad Control.
## Format Selection Guide
| Purpose | Recommended Formats |
| - | - |
| **Continuous Exposure** | Banner |
| **High Visibility** | Interstitial, Splash |
| **User Rewards** | Rewarded |
| **Natural Exposure** | Native |
| **Events/Promotions** | Popup |
***
## Banner
A rectangular ad displayed in a fixed area. The most common format, typically shown as a strip at the top or bottom of the screen.
**Sizes**
| Name | Size | Description |
| - | - | - |
| **MREC** | 300x250 | Recommended for PC & Mobile |
| **Mobile** | 320x50 | Recommended for Mobile |
| **Mobile Large** | 320x100 | Recommended for Mobile |
| **Leaderboard** | 728x90 | Recommended for PC top placement |
| **Half Page** | 300x600 | Recommended for deep scroll areas |
| **Landscape 16:9** | 320x180 | Recommended for video ads |
| **Portrait 9:16** | 180x320 | Recommended for video ads |
| **Custom** | Specify directly | |
All sizes support video creatives. For more details, see [Video Ad Units](/ad-unit/video).
### Banner Template
Using a template automatically applies background and padding around the ad creative. It naturally fills empty space when the creative size is smaller than the Ad Unit.
**Background Color**
If not using a template, you can set the background color directly. Choose a color that matches your app or website design.
### Carousel Banner
When multiple campaigns are running, carousel is activated to rotate ads. Ads are displayed in random order to prevent performance concentration on the first ad.
Video banners cannot use carousel.
**Carousel Activation**
Select whether to use carousel when creating an Ad Unit.
| Situation | Behavior |
| - | - |
| **Multiple Campaigns** | Carousel is created and ads rotate during display |
| **Single Campaign with Multiple Creatives** | Creatives are randomly displayed at equal rates without carousel |
**Indicator Display**
Shows which ad is currently displayed. Appears in the bottom right as "current position/total count" format (e.g., 1/3).
**Rolling Time**
The interval at which the carousel automatically switches. Default is 3 seconds, with 3-4 seconds recommended per ad.
**Operation Policy**
| Item | Details |
| - | - |
| **Maximum Ads** | 20 |
| **Activation Condition** | Carousel operates only with 2 or more ads |
***
### Is Carousel Really Effective?
While it seems efficient to show multiple ads in limited space, actual monetization data and user psychology show it's not as effective as expected.
**Viewability Decline**
Only the first slide has exposure effect. Users quickly scroll without waiting 3-5 seconds for the next slide. Later ads load but aren't actually seen, creating 'phantom impressions' that can lower bid prices over time.
**Divided Attention**
CTR is highest when a single static banner delivers a clear message. When images constantly change and multiple messages roll, users feel fatigued by information processing and tend to look away.
**Banner Blindness**
Users tend to recognize 'moving sliders' as ad areas and ignore them. Visibility is lower compared to native ads that naturally blend into content.
**Recommended Alternatives**
| Method | Description |
| - | - |
| **Single Banner** | Display the best-performing ad permanently to improve CTR |
| **Add Separate Ad Units** | Add Ad Units in different locations instead of carousel to secure exposure opportunities |
Carousel has good 'space efficiency' but poor 'ad efficiency (revenue)'. If maximizing revenue is your goal, we recommend single banners.
***
## Splash
An ad displayed when the app first starts. Occupies the entire screen for excellent brand first impression effect.
**Size**
| Name | Size | Description |
| - | - | - |
| **Bottom 4:3** | 360x270 | |
**Options**
| Option | Description |
| - | - |
| **Background Color** | Set splash background color |
| **Display Duration** | Time the ad is displayed |
| **Dark Mode** | Support for Dark Mode creative |
**Features**
| Item | Details |
| - | - |
| **Impression Product** | Does not support clicks, click count shows 0 in reports |
| **Preloading** | Ad information is preloaded to user devices 72 hours before campaign start |
Creative changes may not be reflected immediately. Finalize creatives and complete review 72 hours before campaign start.
***
## Popup
An ad displayed as a popup on part of the screen. Exposed at bottom or center, with high visibility as it appears covering the screen.
**Sizes**
| Name | Size | Description |
| - | - | - |
| **Bottom Landscape** | 360x240 | |
| **Bottom Portrait** | 360x450 | |
| **Bottom Landscape 16:9** | 320x180 | Recommended for video ads |
| **Bottom Portrait 9:16** | 180x320 | Recommended for video ads |
| **Bottom Custom** | Specify directly | |
| **Center Square 1:1** | 360x360 | |
| **Center Landscape 16:9** | 320x180 | Recommended for video ads |
| **Center Portrait 9:16** | 180x320 | Recommended for video ads |
| **Center Custom** | Specify directly | |
### Templates
Popup ads offer two templates.
| Template | Description |
| - | - |
| **Ad Performance Optimization** | Template with enhanced click prompts, selectable CTA button position |
| **Don't Show Today** | Template convenient for users to close ads |
### Close Options
**Close by Tapping Outside**
| Setting | Behavior |
| - | - |
| **ON** | Ad closes when touching outside ad area |
| **OFF** | Ad can only be closed with close button |
**Outside Area Background Brightness**
Adjusts darkness of background behind popup. Choose from 80 (dark), 55 (medium), 30 (bright).
**Don't Show Today**
| Option | Description |
| - | - |
| **Don't Show Ads Today** | Don't display entire Ad Unit for today |
| **Don't Show This Ad Today** | Don't display only this campaign for today |
### Popup Carousel
When multiple campaigns are running, ads rotate in carousel format.
**Carousel Display Count Limit**
| Setting | Behavior |
| - | - |
| **OFF** | Display up to 20 (system default) |
| **ON** | Specify maximum between 1-20 (default 5) |
Limiting carousel count to 3-4 increases user focus and improves CTR.
**Display Priority**
When available ads exceed carousel limit, they are selectively displayed based on the following priority.
| Priority | Criteria |
| - | - |
| **1** | Ads with Targeting settings prioritized |
| **2** | Ads with smaller Targeting pool prioritized |
| **3** | Ads with lower Campaign progress prioritized |
| **4** | CPP ads displayed randomly |
If carousel limit is less than sold slots, ads are randomly selected for display. As carousel count increases, ad efficiency decreases, so we recommend adjusting slot count to match carousel limit.
***
## Native
An ad that naturally blends with app or website content design. Article-style ads in news feeds are typical examples, with freely customizable UI.
**Sizes**
| Name | Size | Description |
| - | - | - |
| **Mobile Wide 1.91:1** | 600x314 | Recommended for Mobile |
| **PC Wide 1.91:1** | 1200x628 | Recommended for PC |
| **Square 1:1** | 360x360 | |
| **Landscape 16:9** | 320x180 | Recommended for video ads |
| **Portrait 9:16** | 180x320 | Recommended for video ads |
| **Custom** | Specify directly | |
| **No Image/Video** | | Ad performance may decrease without image/video |
### Native Default Fields
Default fields recommended for better ad performance.
| Item | Description |
| - | - |
| **Advertiser Logo** | 256x256px or larger |
| **Advertiser Name** | 10-20 characters recommended |
| **Call-to-Action (CTA) Button** | When activated, advertisers can select CTA text |
| **Ad Headline** | Text label, max character limit configurable |
| **Ad Description** | Text label, max character limit configurable |
### Native Additional Fields
Custom fields can be set for additional information like background color, tags, posting date, engagement metrics.
### Native Options
**AD Mark Display**
Displays 'AD' watermark at top right of ad so users recognize it as advertising. If disabled, users may not recognize it as an ad.
**Accept Dark Mode Creatives**
When enabled, you can accept creatives for dark mode.
***
## Interstitial
A full-screen ad covering the app/web screen. Typically exposed when user activity temporarily stops, such as when a game level ends or page transitions.
**Sizes**
| Name | Size | Description |
| - | - | - |
| **Portrait** | 320x480 | Full image type, fills entire screen with ad image |
| **Square 1:1** | 360x360 | Structured layout, includes title/content/CTA button |
### Close Button Delay Time
Sets time until close button appears after ad is displayed. Users cannot close the ad during delay time, so set appropriate time to balance ad effectiveness and user experience.
High exposure, CTR, and conversion rate are advantages, but displaying too frequently can harm user experience (UX).
***
## Rewarded
An ad that encourages video viewing in exchange for in-app rewards. When users watch the ad to completion, rewards such as items, points, or lives are provided. Frequently used in mobile games.
**Size**
| Name | Size | Description |
| - | - | - |
| **Portrait 9:16** | 180x320 | Recommended for video ads |
### Advantages of Rewarded Ads
| Target | Benefits |
| - | - |
| **App Developers** | Maximize revenue with high ad pricing and completion rates |
| **Users** | Acquire rewards without resistance through voluntary viewing |
| **Advertisers** | High visibility and guaranteed complete viewing |
***
## Next Steps
Check video creative specifications and supported formats.
# Managing Ad Units
Source: https://docs.adrop.io/ad-unit/manage
Guide on how to modify or delete created Ad Units.
Modifying Ad Units can affect running campaigns and Ad Products. Popup ads in particular are reflected immediately upon modification, requiring caution. When possible, we recommend modifying when there are no campaigns or creating new units.
***
## Modifying
### Modification Process
Select the unit to modify from the **Ad Units** menu.
Click the **\[...]** button at the top right of the detail page and select **Modify Unit**.
Change settings and click **Save**.
### Modifiable Items
| Item | Modifiable | Notes |
| - | - | - |
| **Unit Name** | O | |
| **Format-specific Options** | O | |
| **Dark Mode Support** | O | |
| **Formats** | Conditional | If before SDK integration and not included in Ad Products |
| **Size** | Conditional | If before SDK integration and not included in Ad Products |
| **Ad Unit ID** | X | |
### Campaign Reflection Timing
| Formats | Reflection Timing |
| - | - |
| **Popup** | **Immediately reflected** in running campaigns |
| **Banner, Interstitial, Rewarded, Native, Splash** | **Reflected from new campaigns** |
Popup ads are applied immediately to running campaigns upon modification, requiring caution.
### Modification Cautions
* **Format/Size Modification Restrictions**: Units integrated with SDK or included in Ad Products cannot change format and size.
* **Dark Mode Activation**: Running campaigns will display light mode creatives.
***
## Deleting
### Deletion Process
Select the unit to delete from the **Ad Units** menu.
Click the **\[...]** button at the top right of the detail page and select **Delete Unit**.
Enter the unit name to confirm deletion.
### Deletion Cautions
* Deleted Ad Units **cannot be recovered**.
* Ad Units included in running campaigns cannot be deleted. End the Campaign first.
* Past report data for that unit is retained even after deletion.
* Ads will not display if requests are made with a deleted Ad Unit ID.
***
## Next Steps
Check how to monetize empty inventory.
***
## FAQ
Units integrated with SDK or included in Ad Products cannot change format and size. We recommend creating a new unit and gradually transitioning.
Formats other than Popup are not immediately reflected in running campaigns. Modifications apply from newly created campaigns.
If the unit's format or size doesn't match conditions set in the Ad Product, that product won't appear in the Campaign Booking list. Check creative set conditions in Ad Product settings.
There is no impact on already created campaigns. After deletion, new campaigns cannot be booked with products that included that unit.
# What are Ad Units?
Source: https://docs.adrop.io/ad-unit/overview
Ad Units are the foundational concept for monetizing apps and websites. Understanding this concept will greatly help you develop strategies to increase revenue.
## What is an Ad Unit?
An **Ad Unit** is simply a **frame (space) where ads can be displayed**.
It refers to an area where code is embedded in a specific location of an app or website to display ads, and Ad Control fills this Ad Unit with the most appropriate creative in real-time.
Each Ad Unit has a unique **Ad Unit ID**. Through this ID, the system identifies where the ad request originated and delivers ads that match the size and format.
***
## Types of Ad Units
These are the main Ad Units available in Ad Control.
| Formats | Description | Features |
| - | - | - |
| **Banner** | Fixed display in banner form at the top or bottom of the screen | Continuous exposure without interfering with content usage |
| **Interstitial** | Large ad covering the entire screen | High visibility leads to higher CTR and pricing |
| **Rewarded** | Provides in-app rewards upon completion of viewing | Less resistance as users voluntarily choose to watch |
| **Native** | Ad that naturally blends with content design | Minimizes resistance with less apparent advertising feel |
| **Popup** | Displayed as a popup on part of the screen | Bottom/center positioning with various closing options |
| **Splash** | Displayed when launching the app | Achieves high visibility at app start |
***
## Why is Ad Unit Management Important?
Simply adding more ads doesn't increase revenue. The key is **which Ad Units you place and where**.
* **Placement Strategy**: Placing appropriate Ad Units where users' attention lingers increases CTR.
* **Diversity**: Rather than relying only on banners, mixing in Rewarded ads or Interstitial ads can maximize revenue (eCPM).
* **Optimization**: Analyze performance by Ad Unit on the Dashboard, and try changing the position or format of Ad Units with low revenue.
***
## Relationship Between Ad Units and Ad Products
| Category | Description |
| - | - |
| **Ad Units** | Individual areas where ads are actually displayed (e.g., home top banner, mid-feed native) |
| **Ad Products** | Units that bundle Ad Units for sale (e.g., "Premium Package" = home banner + feed native) |
Advertisers purchase Ad Products, and ads are displayed on the Ad Units included in those products.
***
## Table of Contents
1. ⚡ [Quickstart: Creating Your First Ad Unit](/ad-unit/quickstart)
2. [Ad Unit Formats](/ad-unit/formats)
3. [Video Ad Units](/ad-unit/video)
4. [Supporting Dark Mode](/ad-unit/dark-mode)
5. [Creating Ad Units](/ad-unit/create)
6. [Managing Ad Units](/ad-unit/manage)
7. [Maximize Revenue with Backfill Ads](/ad-unit/backfill)
# Quickstart: Creating Your First Ad Unit
Source: https://docs.adrop.io/ad-unit/quickstart
Time required: About 5 minutes
## Before You Start
* App name
* Package name (Android) / Bundle ID (iOS) / Domain (Web)
## Step-by-Step Guide
To create an Ad Unit, you must first register an app.
1. Access Console → Select **Ad Units** menu
2. Click **Add App**
3. Enter app information
* **App Name**: Search store or enter manually
* **Platform**: Android / iOS / Web
* **Package Name** (Android) / **Bundle ID** (iOS) / **Domain** (Web)
4. **Save** → App ID is issued
Apps registered on Google Play or App Store can be quickly added through search.
1. Select registered app
2. Click **Add Ad Unit**
3. Enter Ad Unit information
* **Formats**: Choose from Banner, Interstitial, Rewarded, Native, Popup, Splash
* **Size**: Available sizes vary by format
* **Ad Unit Name**: Name for management (e.g., Home\_Top\_Banner)
4. **Save** → Ad Unit ID is issued
You can also create Ad Units from the [Create Ad Products](/ad-product/quickstart) screen.
## Completion
Once created, you'll receive an App ID and Ad Unit ID.
| Item | Where to Find | Purpose |
| - | - | - |
| **App ID** | Settings > App | Used for SDK initialization |
| **Ad Unit ID** | Ad Unit list | Used for ad requests |
Provide the issued IDs to your developer. The developer will integrate the SDK to display ads on that Ad Unit.
## Next Steps
**Learn More**
Check the sizes and options for each format.
**Move to Next Quickstart**
Compose Ad Units into products.
***
## FAQ
Ad Units are individual areas where ads are actually displayed, while Ad Products are units that bundle Ad Units for sale. Advertisers purchase Ad Products, and ads are displayed on the Ad Units included in those products.
To increase the Fill Rate for Backfill ads, we recommend standard sizes widely used by global ad networks.
| Size | Description |
| - | - |
| **320x50** | Basic mobile banner, highest Fill Rate |
| **320x100** | Large banner |
| **300x250** | Medium rectangle, high eCPM |
We recommend creating new Ad Units when possible. The Ad Unit may already be in use by deployed apps, so modifications could cause unexpected issues.
# Video Ad Units
Source: https://docs.adrop.io/ad-unit/video
Introducing Ad Units that support video creatives.
## Supported Formats
Video ads can be used in the following formats.
| Platform | Supported Formats |
| - | - |
| **Android / iOS** | Banner, Popup, Native |
| **Web** | Banner, Native |
***
## Supported Sizes
All banner, popup, and native sizes support video, including custom sizes.
Video controller can be toggled on/off in the unit settings.
| Controller | Playback |
| - | - |
| **On** | Plays with controller and sound toggle |
| **Off** | Plays muted with auto-loop |
***
## Video Options
You can configure video-related options when creating an ad unit.
### Accept Video Creatives
Enable or disable video creative acceptance per unit. When enabled, advertisers can register video creatives for that unit.
### Video Controller
Video controller can be toggled on/off for all sizes.
* Controller on: Sound can be toggled (muted by default)
* Controller off: Plays muted with auto-loop
A minimum size of 300×150dp is recommended for the video controller to display properly.
### Video Skip Delay
Skip delay can be set on units with the video controller turned on. After the set time, a skip button appears allowing users to skip the ad.
***
## Creative Specifications
### Video
| Item | Specifications |
| - | - |
| **Format** | MP4, MOV, WebM, MKV, AVI |
| **Size** | Maximum 1GB |
| **Aspect Ratio** | Same as the ad unit size |
| **Duration** | 3 seconds or more, under 30 seconds |
| **Resolution** | 720p or higher recommended (based on shorter side) |
### Thumbnail
The cover image displayed before video playback.
| Item | Specifications |
| - | - |
| **Format** | JPG, JPEG, PNG, WebP |
| **Size** | Same aspect ratio as the ad unit |
| **File Size** | Maximum 220KB |
***
## Delivery Format
Videos are converted and provided in two formats.
| Format | Description |
| - | - |
| **HLS** | Automatically switches to optimal quality based on network conditions |
| **Progressive MP4** | Sequential playback as a single file |
***
## Limitations
* Videos cannot be used in Banner carousel.
* Dark mode is not available for video creatives.
* Interstitial, Rewarded, and Splash formats have separate video size options. See [Ad Unit Formats](/ad-unit/formats) for details.
***
## Measuring Performance
Campaigns that include video units expose viewing-quality metrics (Valid Views / Valid View Rate, Completed Views / View-Through Rate) alongside impressions and clicks.
| Where to View | Description |
| - | - |
| **Console > Report** | Video metrics by campaign, ad product, ad unit, and creative |
| **Console > Ad Unit detail > Performance** | Viewing summary for the unit |
| **Console > Ad Product detail > Performance** | Viewing summary for the product |
| **Console > Campaign detail > Performance / Creatives** | Viewing metrics for the campaign and each creative |
See [Analytics & Reports — Video Metrics](/campaign/analytics#video-metrics) for metric definitions.
***
## Next Steps
Check how to configure Dark Mode creatives
# Create Ad Account
Source: https://docs.adrop.io/campaign/ad-account
To run ads, you must first create an ad account. This guide explains how to access the Ad Platform, create an ad account, and set up a payment profile.
***
## Access Ad Platform
Access the Ad Platform via the link provided by the publisher.
1. Open the Ad Platform URL in your browser.
* Format: `https://{publisher-domain}.adrop.io`
2. **Log in** or **sign up**.
***
## Create Ad Account
An ad account is the unit for booking and managing campaigns.
You can create multiple ad accounts within one organization and share payment profiles across them. Separate ad accounts by brand or project for better management.
### How to Create an Account
1. Click the ad account name in the top-left corner
2. Click **\[+ Create]** at the bottom of the dropdown menu
3. Enter account information and click **\[Create]**
Created ad accounts can be switched by selecting them from the dropdown.
### Enter Account Information
| Item | Description |
| - | - |
| **Advertiser Name** | Advertiser or company name (2-20 characters). Used as the ad account name. |
| **Website** | Advertiser website URL (optional) |
| **Country** | Advertiser's country. Affects tax rate and payment currency. |
| **Timezone** | Timezone used for report data display |
| **Language** | Ad Platform interface language |
Campaign schedules and ad delivery are based on the ad product's timezone.
***
## Set Up Payment Profile
A payment profile stores business information and payment methods.
Payment profiles can be shared across multiple ad accounts. You can create your own or be invited by team members to use theirs.
### Create Payment Profile
1. Select **Billing & Payments** from the left menu
2. Click **Add Payment Profile**
3. Enter profile name and click **Create**
### Enter Business Information
| Item | Description |
| - | - |
| **Country** | Billing country (Korea, Japan, India supported) |
| **Address** | City, detailed address, postal code |
| **Business Name** | Corporate name or trade name |
| **Tax ID** | Business registration number (numbers only, no hyphens) |
| **Business License** | Upload business license file (image or PDF) |
| **Primary Email** | Email for tax invoice issuance and settlement notifications |
| **CC Emails** | Additional recipient emails (separate multiple with commas or line breaks) |
**Tax ID & Rates by Country**
| Country | Tax ID | Tax Rate | Tax Name |
| - | - | - | - |
| **Korea (KR)** | Business Registration Number (10 digits) | 10% | VAT |
| **Japan (JP)** | Corporate Number (10 digits) | 0% | Reverse Charge |
| **USA (US)** | EIN (9 digits) | 0% | Reverse Charge |
| **India (IN)** | GSTIN (15 digits) | 0% | Reverse Charge |
For overseas advertisers except Korea, Reverse Charge applies. Invoices do not include tax, and advertisers must report and pay taxes according to their local tax laws.
### Register Payment Method
| Payment Provider | Supported Currency | Description |
| - | - | - |
| **Toss Payments** | KRW | Korean card payments (personal/corporate cards) |
| **Stripe** | Multi-currency | Global card payments |
1. Click the **Add Payment Method** button.
2. Select a payment provider.
3. Enter card information.
4. Click the **Register** button.
Payment profiles can only be used by ad accounts with matching countries.
***
## Share Payment Profile
To share a payment profile with team members, invite them as members.
1. In **Account Modal** > **My Payment Profiles**, select the profile to share
2. Click **Invite Member**
3. Enter the email of the team member to invite
4. Click **Invite**
Invited members can use the payment profile in their own ad accounts.
***
## Manage Ad Account Members
You can invite team members to manage ad accounts together.
### Invite Member
1. Select **Member Management** menu in ad account settings
2. Click **Invite Member**
3. Enter team member's email
4. Select role
5. Click **Invite**
### Role Permissions
| Role | Description |
| - | - |
| **Owner** | All permissions. Can manage payment methods, delete accounts, and manage member permissions |
| **Editor** | Can create/edit campaigns. Cannot delete or manage members |
| **Viewer** | View-only access to data |
| **Guest** | Dashboard and campaign performance view-only |
Only the Owner role can add or remove payment methods.
***
## Next Steps
Learn how to select ad products and book campaigns.
***
## FAQ
Yes, a single payment profile can be shared across multiple ad accounts. However, the payment profile's country and the ad account's country must match.
Yes, you can create multiple ad accounts within one organization. Separate them by brand or project for better management.
Register a new card in the payment profile, then select that card when booking campaigns. Existing cards can be deleted from the payment profile.
# Analytics & Reports
Source: https://docs.adrop.io/campaign/analytics
Learn how to view campaign performance and download reports.
***
## What is a Report?
Reports allow you to view performance data by campaign, ad product, ad unit, and creative. Access them from the **Report** menu in the sidebar.
* Data refresh interval: 10 minutes
* Maximum query period: 1 year
***
## Understanding Metrics
### Basic Metrics
| Metric | Description |
| - | - |
| **Impressions** | Number of times the ad was displayed on user screens |
| **Clicks** | Number of times users clicked the ad |
| **CTR (Click-Through Rate)** | Click rate. Clicks ÷ Impressions × 100 |
Impressions and clicks are each counted at most once per ad request, following industry-standard measurement guidelines.
For CPM/CPC campaigns, ads served to a device at the time of an ad request are what get displayed. As a result, even after a campaign reaches its target impressions/clicks and can no longer serve, or has ended, ads delivered to devices just before that point may still be shown or clicked, and those events will be counted — causing final figures to slightly exceed the configured target.
### Cost Metrics
| Metric | Description |
| - | - |
| **CPM (Cost Per Mille)** | Cost per 1,000 impressions |
| **CPC (Cost Per Click)** | Cost per click |
| **CPP (Cost Per Period)** | Fixed cost per period |
### Video Metrics
These metrics show the viewing quality of campaigns that include video creatives. They are available at the campaign, ad product, ad unit, and creative levels.
| Metric | Description |
| - | - |
| **Valid Views** | Number of times the video was played for at least 3 seconds |
| **Valid View Rate** | Valid Views ÷ Video Impressions × 100 |
| **Completed Views** | Number of times the video was played to the end |
| **View-Through Rate (VTR)** | Completed Views ÷ Video Impressions × 100 |
Video metrics are only counted for items where video creatives were served. Campaigns running only image creatives will show 0.
***
## Viewing Reports
### Jump in from a campaign or product
Clicking the **Performance Report** button on a campaign or ad product detail screen opens the Report menu in a new window with that campaign or product filter pre-applied. You can immediately apply date ranges, segments, and additional filters to continue your analysis.
### Date Range Selection
Set the query period at the top of the screen. You can select preset periods (Today, Yesterday, Last 7 days, Last 30 days) or specify custom start and end dates using the date picker.
### Query Criteria (Tabs)
Select the data type to view from the top tabs.
| Tab | Description |
| - | - |
| **Campaign** | Performance by campaign |
| **Ad Product** | Performance by ad product |
| **Ad Unit** | Performance by ad unit |
| **Creative** | Performance by creative |
### Segments
You can further segment the data. For example, selecting the **Creative** segment in the Campaign tab lets you view creative-level performance for each campaign.
### Filtering
You can filter to view only specific campaigns, ad products, ad units, or creatives. Adding a filter displays only those items in the table. Multiple selections are supported.
### Daily/Hourly View
Check the checkbox to view data broken down by day or hour.
| Option | Description |
| - | - |
| **Daily** | Display data separated by date |
| **Hourly** | Display data separated by hour |
### Edit Columns
Click the **Edit Columns** button to select which columns to display in the table. You can also drag and drop to reorder columns. Your settings are automatically saved and persist on your next visit.
### Sorting
Click a column header to sort data by that column. Click again to change the sort direction.
***
## Downloading Reports
### CSV Export
Click the **Export CSV** button to download the currently viewed data as a CSV file.
| Item | Description |
| - | - |
| **File Name** | `report-{tab}_{segment}-{start_date}_{end_date}.csv` |
| **Delivery Method** | Download link sent via email when file is ready |
### Column Selection
You can select only the columns you need when downloading. Default columns are provided per tab and segment, and only selected columns are included in the CSV.
Large datasets are processed in the background. You'll receive an email notification when complete.
***
## Next Steps
Review guidelines for splash ads.
***
## FAQ
Report data is refreshed every 10 minutes. If you need real-time data, check the campaign detail screen.
Click the **Export CSV** button on the report screen. When the file is ready, a download link will be sent to your email.
You can query up to 1 year. If you need older data, contact the publisher.
# Booking Campaigns
Source: https://docs.adrop.io/campaign/booking
Learn how to select an ad product and book a campaign.
***
## Booking Steps
Ad product, period, targeting
Campaign name, budget
Contact info, payment profile
Review booking details and submit
***
## Step 1: Ad Product Selection
### Ad Product
1. Click the **Create Campaign** button.
2. Select your desired ad product from the list.
| Item | Description |
| - | - |
| **Ad Product Name** | Name of the ad product |
| **Billing Type** | CPM, CPC, or CPP |
| **Price** | Cost based on billing type |
**Billing Types**
You are charged each time the ad is displayed 1,000 times.
You are charged each time a user clicks the ad.
A fixed cost is charged per time period. Suitable when you want to secure ad inventory for a specific duration.
### Setting the Period
Select the start and end dates.
| Item | Rule |
| - | - |
| **Max Start Date** | Approximately 5 months from booking date |
| **Max Campaign Duration** | Approximately 3 months from start date |
Time zone follows the publisher's settings. Check the time zone displayed next to the campaign period.
**CPP Ad Products**
| Item | Description |
| - | - |
| **Minimum Purchase Period** | Some ad products may have a minimum purchase period. |
| **Slots** | Multiple advertisers can be displayed during the same period. Check the number of slots set for the ad product. |
Bookings with start dates within 7 days are classified as imminent bookings. These may be rejected depending on publisher availability, so we recommend contacting the publisher directly to request expedited review.
### Selecting Targeting
Setting targeting allows you to show ads only to specific audiences rather than everyone. Select the options you want from the targeting offered by the publisher, and choose whether to include or exclude each option you select. In each targeting where you include options, the ad is shown only when at least one included option matches. If any excluded option matches, the ad is not shown, even if the included options also match.
**Targeting Types**
Choose **who** to show ads to. Display only to specific groups based on user attributes.
Example: Women in their 20s, gaming enthusiasts
Choose **where** to show ads. Display only on specific placements based on the content topic the user is viewing.
Example: Sports article pages, finance news section
You can choose include or exclude options in only one context targeting.
**Required Include / Required Exclude**
Publishers can preset required includes or excludes for targeting options per ad product.
| Option | Description |
| - | - |
| **Required Include** | The option set by the publisher is automatically included in every campaign, and advertisers cannot deselect it. If an advertiser includes other options in the same targeting, ads are also shown for those options. Example: If "Sports" is a required include and no other options in that targeting are available to select, ads only show on sports-related placements |
| **Required Exclude** | Ads don't display for that targeting option. Example: With "Politics" excluded, ads won't show on politics-related placements |
Required includes/excludes are set by the publisher and cannot be changed by advertisers.
**Premium Rate**
Selecting targeting applies a premium rate to the ad price. Selecting multiple targetings accumulates the premium rates. The premium rate applies once per targeting, even if you include or exclude several options in it. A targeting with required includes or excludes always applies its premium rate.
| Example | Calculation |
| - | - |
| **Base CPM** | \$10 |
| **Age Targeting** | +10% |
| **Gender Targeting** | +20% |
| **Final CPM** | $13 (= $10 × 1.3) |
Setting targeting narrows your ad audience, which may reduce expected impressions and clicks.
***
## Step 2: Campaign Info
### Campaign Name
Enter a name to identify your campaign.
### Budget Setting
**CPM/CPC Ad Products**: Enter your total budget. Daily allocation is automatically distributed evenly based on the period.
**CPP Ad Products**: A fixed amount based on the period is applied.
### Expected Performance
After entering your budget, expected performance appears on the right side of the screen.
| Item | Description |
| - | - |
| **Budget** | Total budget amount entered |
| **Available Budget** | Amount actually used for ad delivery after targeting premiums |
| **Expected Impressions** | Expected number of ad impressions based on budget |
| **Expected Clicks** | Expected number of clicks |
| **Expected CTR** | Expected click-through rate |
Expected performance is an estimate based on historical data and may differ from actual results.For CPM/CPC campaigns, ads served to a device at the time of an ad request are what get displayed. Even after a campaign reaches its target impressions/clicks and can no longer serve, or has ended, ads delivered to devices just before that point may still be shown or clicked, causing final figures to slightly exceed the configured target.
***
## Step 3: Payment Info
### Contact Info
| Item | Description |
| - | - |
| **Contact Name** | Campaign contact person's name |
| **Email** | Reachable email address |
### Payment Method
Prepay with a registered card.
**Timing**: Charged immediately upon campaign submission
Contact the publisher directly for payment.
**Timing**: Separate instructions after campaign approval
Available payment methods may vary by publisher settings. Card payment requires a card registered to your payment profile.
### Business Info
Business information used for tax invoice issuance. Payment profiles registered to your ad account are displayed. If you don't have a payment profile, see [Creating an Ad Account](/campaign/ad-account).
***
## Step 4: Confirm
Review all settings from previous steps.
### Campaign Info
| Item | Details |
| - | - |
| **Ad Product** | Selected ad product name, discount rate |
| **Billing Type** | Billing type and price (adjusted price if premiums apply) |
| **Campaign Period** | Start date \~ End date |
| **Country** | Ad display country |
| **Advertiser** | Advertiser name and link |
| **Contract Contact** | Contact name and email |
| **Payment Method** | Card payment or Manual payment |
| **Business Info** | Business name, registration number, email |
| **Targeting** | Selected targeting options and premium rates |
### Booking Amount
| Item | Description |
| - | - |
| **Booking Amount** | Total budget amount (VAT excluded) |
| **Expected Impressions** | Expected ad impressions |
| **Expected Clicks** | Expected clicks |
### Booking Terms Agreement
You must agree to the booking terms to submit. Click the **View Terms** button to review the terms.
### Save as Draft
You can save as a draft before completing the booking. Draft campaigns are displayed with **Draft** status.
***
## After Submission
### Card Payment
| Step | Description |
| - | - |
| **1. Submit** | Payment processes immediately upon submission |
| **2. Payment Complete** | Campaign enters pending review status upon successful payment |
| **3. Review** | Publisher reviews the campaign |
| **4. Approve/Reject** | Full refund if rejected |
Payment may fail due to card limit exceeded, expired card, etc. Select a different card or update your card information and try again.
### Manual Payment
| Step | Description |
| - | - |
| **1. Submit** | Campaign submitted without payment |
| **2. Review** | Publisher reviews the campaign |
| **3. Approval** | Campaign proceeds after approval |
| **4. Settlement** | Settle directly with publisher |
### Review Results
| Result | Description |
| - | - |
| **Approved** | Campaign status changes to **Booked**. Automatically changes to **Live** on start date. |
| **Rejected** | Rejection reason is provided. Full refund for card payments. You can modify and resubmit the campaign. |
If no creatives are registered, ad delivery won't start even if the campaign is approved.
***
## Next Steps
Learn how to register ad creatives.
***
## FAQ
Yes, a premium rate is applied for each targeting. Selecting multiple targetings accumulates the premium rates. You can check the final price on the booking screen.
Publishers can set minimum budgets per ad product. Check the minimum budget on the booking screen.
Bookings within 7 days of the start date are classified as imminent bookings and may be rejected depending on publisher availability. If you have an urgent timeline, contact the publisher directly to request expedited review.
Card payment is charged immediately upon submission as prepaid, with automatic settlement after campaign completion and refund of unused amounts. Manual payment settles only actual spend directly with the publisher after campaign completion.
# Registering Creatives
Source: https://docs.adrop.io/campaign/creative
Learn how to register ad creatives for your campaign. Creatives are the ad content displayed to users, consisting of images, videos, text, and more.
***
## Creative Section
The creative section is located at the bottom of the campaign detail screen.
### Required Creatives
The creative types you need to register are displayed based on the ad product. Each card shows the creative type, specifications, and registration button.
### Registered Creatives List
Already registered creatives are displayed in a table. Click on a creative to view details and make edits.
***
## Registering Creatives
Click the **Add Creative** button on the required creative card to open the creative registration screen.
### Common Input Fields
These fields are required for all ad formats.
#### File Upload
Drag and drop files or click **Select File** to upload. After uploading, check the specification validation results and re-upload if there are errors.
**Image Creatives**
| Item | Specification |
| - | - |
| **Format** | JPG, PNG, WebP, GIF |
| **Max Size** | 10 MB |
| **Min Resolution** | 3× the ad specification |
| **Aspect Ratio** | Same as ad specification (1% tolerance) |
Recommended resolution examples:
| Ad Spec (dp) | Min Resolution (px) |
| - | - |
| **300×250** | 900×750 or higher |
| **320×180 (16:9)** | 960×540 or higher |
Splash ads do not support GIF.
**Dark Mode Creatives**
If the publisher supports dark mode, you can register separate images for dark mode.
| Item | Description |
| - | - |
| **Supported Formats** | Banner, Native, Popup, Splash (excluding video) |
| **Display Method** | Automatically selected based on user's device theme settings |
| **Fallback Behavior** | Light mode creative is shown if dark mode creative is not available |
| **Review** | Light/dark creatives are reviewed together; if either violates policies, both are rejected |
Whether dark mode creative registration is available depends on the publisher's ad unit settings. If the dark mode input field is not displayed, the publisher does not support dark mode.
**Video Creatives**
For products that support video, you can select either image or video type when registering creatives. Dark mode option is not available when video is selected.
| Item | Specification |
| - | - |
| **Format** | MP4 (recommended), MOV, WebM, MKV, AVI |
| **Max Size** | 1 GB |
| **Duration** | 3 seconds or more, under 30 seconds |
| **Min Resolution** | 720p or higher recommended |
| **Aspect Ratio** | Same as the ad unit size |
Video creatives require a cover image, which is displayed while the video is loading or before playback.
| Item | Specification |
| - | - |
| **Format** | JPG, PNG, WebP |
| **Aspect Ratio** | Same as video |
On iOS, the bottom 34dp is the Safe Area. Avoid placing important text or logos there.Videos are automatically transcoded to multiple quality levels after upload. Depending on the video length, transcoding may take a few minutes, so please allow sufficient time.
#### Creative Name
A name to identify the creative. Enter a name that makes it easy to distinguish in the creative list.
#### Destination URL
Enter the page URL to navigate to when the ad is clicked. The URL you enter is used as-is, so you can freely use tracking parameters or deep links.
| Type | Example | Description |
| - | - | - |
| **Web URL** | `https://example.com/landing` | Standard landing page |
| **Tracking URL** | `https://tracker.example.com/click?campaign_id=123&redirect=...` | URL with conversion tracker |
| **Deep Link** | `myapp://product/123` | Navigate to specific screen in app |
| **Universal Link** | `https://example.com/app/product/123` | iOS/Android common deep link |
| **App Store** | `https://play.google.com/store/apps/details?id=...` | Drive app installs |
You don't need to separate Android and iOS—universal links or app links work across platforms. Configure fallback handling for cases when the app is not installed through your deep link service provider.
You can enter tracking links from AppsFlyer, Adjust, Branch, etc. as-is. URLs are passed without modification.
***
### Native Ad Additional Fields
Native ads are configured to blend naturally with the app's content. The following fields are displayed based on the ad unit settings configured by the publisher.
#### CTA Button
A call-to-action button. Only displayed if the publisher has enabled CTA buttons.
You can select button text appropriate for the ad purpose, such as "Learn More", "Buy Now", or "Install". You can also set the button background color and text color.
#### Advertiser Profile
Displays advertiser information. Only displayed if the publisher has enabled profile display.
| Item | Description |
| - | - |
| **Profile Image** | 256×256px square image |
| **Profile Name** | Advertiser or brand name (max 20 characters) |
| **Profile Link** | URL to navigate to when profile is clicked |
#### Text
Text to be displayed in the ad. Text fields configured by the publisher are displayed.
| Item | Description |
| - | - |
| **Headline** | Ad title |
| **Body** | Ad description |
| **Additional Text** | Extra fields defined by publisher (optional) |
Each text field has a maximum character limit.
***
### Advanced Settings (Optional)
You can set different display schedules for each creative within a campaign. For example, you can show different creatives on weekdays vs. weekends, or show promotional creatives only during specific periods.
**Display period and display time cannot be set for splash ad creatives.** Splash ads follow the campaign schedule as-is.Advanced settings are only displayed if the publisher allows them.
**Display Period**
| Option | Description |
| - | - |
| **Entire Campaign Period** | Displayed from campaign start date to end date. (Default) |
| **Custom Period** | Select specific start/end dates and times within the campaign period. |
When a creative's display period is set shorter than the campaign period, the campaign's **daily target impressions are calculated based on the creative's display period**, not the campaign period. For example, if the campaign runs for 10 days but the creative's display period is 5 days, the daily target impressions are calculated so the budget is fully delivered within those 5 days.
**Display Time**
| Option | Description |
| - | - |
| **Always Display** | Displayed 24 hours a day. (Default) |
| **Specific Time** | Set the same time range for all days. Example: 09:00-18:00 every day |
| **Day-specific** | Set different time ranges for each day. Example: Weekdays 09:00-18:00, Weekends 10:00-22:00 |
Time zone is based on the publisher's time zone set in the campaign.
***
### Submit
Click the **Submit** button to change the creative status to pending review.
***
## Next Steps
Learn how to check campaign status and manage creatives.
***
## FAQ
Reviews are typically completed within 1-3 business days. You'll receive an email notification with the review result.
Yes, you can register multiple creatives for a single campaign. Approved creatives will be displayed in rotation.
Check the rejection reason and modify the creative, then resubmit. Modified creatives will go through review again.
Videos are automatically transcoded to multiple quality levels after upload. Depending on the video length, transcoding may take a few minutes, so please allow sufficient time.
# Operating Campaigns
Source: https://docs.adrop.io/campaign/operation
Learn how to check and manage campaign and creative status.
***
## Campaign Status
You can check the current status from the campaign list or detail screen.
For ads to be displayed, both the **campaign** and **creatives** must be approved. Even if the campaign is approved, ads won't be displayed if creatives aren't approved, so be sure to register and submit creatives for review after campaign approval.
### Basic Flow
Entering campaign information. Submitting changes status to Submitted.
Publisher is reviewing the campaign. Changes to Booked if approved, Rejected if declined.
Review approved, waiting for start date. Automatically changes to Live on start date.
Ads are being displayed. Automatically changes to Closed on end date.
Campaign has ended.
### Other Statuses
| Status | Description | Transition |
| - | - | - |
| **Rejected** | Declined in review | Edit and resubmit → Submitted |
| **Paused** | Suspended from Booked/Live | Resume → Previous status |
| **Cancelled** | Campaign cancelled | - |
***
## Editing Campaigns
Campaigns can only be edited when in Draft or Rejected status. If submitted for review, you must cancel and recreate. After approval, advertisers cannot edit directly. Contact the publisher if changes are needed.
**Cannot be edited after approval**: Ad product, budget, billing type
* Editable: Draft, Rejected
* Not editable: Submitted, Booked, Live, Paused, Cancelled, Closed
***
## Pausing/Resuming Campaigns
You can pause campaigns that are Booked or Live. Pausing immediately stops ad delivery and budget spending, and you can resume anytime. While paused, only the end date can be extended. The campaign will automatically end when the end date passes.
Unused budget may not be automatically refunded due to pausing. Consult with the publisher before pausing.
* Can pause: Booked, Live
* When paused: Ad delivery stops, budget spending stops, only end date extension allowed
***
## Cancelling Campaigns
Only campaigns in Draft, Submitted, or Rejected status can be cancelled. Cancelling while submitted results in a full refund. After approval (Booked, Live, Paused), advertisers cannot cancel directly—contact the publisher if needed. Cancelled campaigns cannot be restored.
* Can cancel: Draft, Submitted, Rejected
* Cannot cancel: Booked, Live, Paused (contact publisher)
***
## Duplicating Campaigns
You can quickly create a new campaign with the same settings as an existing one.
### How to Duplicate
1. Click the menu (⋯) on the campaign detail screen.
2. Select **Duplicate**.
3. A duplicated campaign is created in **Draft** status.
4. Set the budget and other required fields, then submit.
### What Gets Duplicated
| Item | Duplicated |
| - | - |
| **Ad Product** | Yes |
| **Targeting** | Yes (if still valid) |
| **Frequency Cap** | Yes |
| **Payment Type** | Yes |
| **Contact Info** | Yes |
| **Creatives** | Yes (valid creatives only, requires re-review) |
If categories or groups in the original campaign's targeting have been deleted/deactivated, that targeting will be reset. Always verify targeting settings after duplication.
### What Gets Reset
| Item | Value After Duplication |
| - | - |
| **Budget** | 0 (must be set) |
| **Start Date** | 7 days from current date |
| **End Date** | Start date + original campaign duration |
| **Title** | Original title + unique ID |
| **Status** | Draft |
| **Creative Status** | Under Review (requires re-review) |
### Creative Duplication Rules
* Rejected or deleted creatives are not duplicated.
* All duplicated creatives are set to **Under Review** status and require re-review.
* Creatives incompatible due to ad unit changes are excluded.
### Cannot Duplicate When
Campaigns cannot be duplicated in these cases:
* Ad product has been deleted
* Ad product's creative set is missing or needs modification
***
## Creative Status
| Status | Description |
| - | - |
| **Under Review** | Awaiting creative review |
| **Approved** | Ready for ad delivery |
| **Rejected** | Creative review rejected, modification needed |
| **Paused** | Advertiser stopped delivery |
If a creative is rejected, you can check the rejection reason on the detail screen. Modify the creative according to the reason and resubmit. For detailed review criteria, see [Ad Policy](/campaign/policy).
***
## Editing Creatives
Creatives can be edited until the campaign is cancelled or closed. Changing creative content (file, destination URL, text, etc.) requires re-review, and the creative won't be displayed during review. Changing only the creative name is reflected immediately without review.
* Editable: When campaign is not Cancelled/Closed
* Content changes: Requires re-review, delivery paused during review
* Name changes only: Reflected immediately without re-review
***
## Deleting Creatives
Creatives can be deleted until the campaign is cancelled or closed. Deleted creatives cannot be restored, so proceed carefully.
* Can delete: When campaign is not Cancelled/Closed
* After deletion: Cannot be restored
***
## Pausing/Resuming Creatives
Pausing an approved creative stops delivery of that creative. If there are other approved creatives, those will be displayed instead, with no impact on campaign budget spending. Paused creatives can be resumed anytime.
* Can pause: Approved creatives
* When paused: Only that creative stops delivery, other creatives continue
***
## Refunds
Refunds apply only to card payment campaigns. For manual payments, settle directly with the publisher.
* Campaign review rejected: Full refund
* Advertiser cancels (under review): Full refund
* Publisher cancels: Unused budget refunded
***
## Notifications
You'll receive email notifications for major status changes to campaigns and creatives.
| Subject | Notification Triggers |
| - | - |
| **Campaign** | Review approved, Review rejected, Campaign started, Campaign ended |
| **Creative** | Review approved, Review rejected |
***
## Settlement
Settlement proceeds when a campaign ends or is cancelled. Settlement procedures vary by payment method.
### Card Payment (Automatic Settlement)
Campaigns paid by card are settled automatically.
| Step | Description |
| - | - |
| **1. Campaign Ends** | Final performance is calculated when campaign ends |
| **2. Statement Issued** | Statement is automatically generated based on actual spend |
| **3. Unused Budget Refunded** | Unused budget is automatically refunded |
Card payment is prepaid. The full budget is charged when submitting the campaign, and unused amount is refunded after completion.
### Manual Payment (Direct Settlement)
Campaigns with manual payment are settled directly with the publisher.
| Step | Description |
| - | - |
| **1. Campaign Ends** | Final performance is calculated when campaign ends |
| **2. Statement Issued** | Statement is generated based on actual spend |
| **3. Direct Payment** | Pay via bank transfer, etc. as instructed by publisher |
Manual payment is postpaid. Only actual spend is charged after campaign ends. Request tax invoices directly from the publisher if needed.
### Viewing Statements
Click the **Statement** button on the campaign detail screen to view the statement.
| Item | Description |
| - | - |
| **Booked Amount** | Total budget set when booking campaign |
| **Spent Amount** | Amount consumed by actual ad impressions/clicks |
| **Refund Amount** | Unused budget (card payment only) |
| **Tax** | Applied taxes such as VAT |
| **Final Settlement Amount** | Final billed/refunded amount |
***
## Next Steps
Learn how to check campaign performance.
***
## FAQ
While paused, only extending the end date is allowed. The campaign will automatically end when the end date passes.
After approval (Booked, Live, Paused), advertisers cannot cancel directly. Contact the publisher if cancellation is needed. Penalty fees may apply.
For card payments, unused budget is automatically refunded after campaign completion. Manual payments only settle actual spend, so there's no separate refund process.
# What is a Campaign?
Source: https://docs.adrop.io/campaign/overview
This guide explains the process by which advertisers book campaigns and register creatives to run ads. It covers everything from the basic concepts of campaigns to booking, payment, and creative registration.
***
## What is a Campaign?
A **Campaign** is the basic unit of ad delivery. Advertisers select an ad product sold by publishers, set their desired period and budget, and book a campaign. Once the campaign is approved, creatives are registered, and ads are displayed when the start date arrives.
### Campaign Workflow
Campaigns progress through the following stages from booking to ad display.
| Stage | Responsible | Description |
| - | - | - |
| **1. Campaign Booking** | Advertiser | Select ad product, set period/budget, choose targeting |
| **2. Payment & Submit** | Advertiser | Enter payment information, agree to terms, and submit |
| **3. Campaign Review** | Publisher | Review campaign content and approve/reject |
| **4. Creative Registration** | Advertiser | Upload image/video creatives, set landing URL |
| **5. Creative Review** | Publisher | Review creative content and approve/reject |
| **6. Ad Display** | System | Automatically display from start date to end date |
Creatives can be registered at the same time as campaign submission, or separately after campaign approval. Both the campaign and creatives must be approved for ads to be displayed.
***
## Campaign Components
### Ad Products
The ad inventory sold by publishers. Advertisers book campaigns by selecting ad products.
| Component | Description |
| - | - |
| **Ad Units** | The placements where ads are displayed (banner, interstitial, native, etc.) |
| **Pricing Model** | CPM, CPC, CPP |
| **Rate** | Cost per impression/click/period |
| **Targeting** | Options to show ads to specific users or content (age group, gender, category, etc.) |
**Pricing Models**
* **CPM** (Cost Per Mille): Cost per 1,000 impressions
* **CPC** (Cost Per Click): Cost per click
* **CPP** (Cost Per Period): Fixed cost per period
### Period & Budget
| Item | Description |
| - | - |
| **Start Date/End Date** | Campaign duration |
| **Total Budget** | Overall campaign budget |
### Creatives
The ad content displayed to users.
| Item | Description |
| - | - |
| **File** | Image (JPG, PNG, WebP, GIF) or video (MP4, MOV, WebM) |
| **Landing URL** | Page to navigate to when clicked |
| **CTA** | Call-to-action button (Learn More, Buy Now, etc.) |
***
## Table of Contents
1. ⚡ [Quickstart: Book Your First Campaign](/campaign/quickstart)
2. [Create Ad Account](/campaign/ad-account)
3. [Book a Campaign](/campaign/booking)
4. [Register Creatives](/campaign/creative)
5. [Operate Campaigns](/campaign/operation)
6. [Analytics & Reports](/campaign/analytics)
7. [Splash Ad Guide](/campaign/splash-guide)
8. [Ad Policy](/campaign/policy)
# Ad Policy
Source: https://docs.adrop.io/campaign/policy
Learn about ad review criteria and policies.
***
## Review Overview
Campaigns and creatives are approved through publisher review. Reviews are conducted to protect ad quality and user experience.
### Review Subjects
| Subject | Review Content |
| - | - |
| **Campaign** | Booking schedule, budget, brand policy |
| **Creative** | Content quality, technical specifications, policy compliance |
### Review Results
| Result | Description |
| - | - |
| **Approved** | Ad can be displayed |
| **Rejected** | Must modify and resubmit (reason provided) |
***
## Campaign Rejection Reasons
| Reason | Description |
| - | - |
| **Already Booked Schedule** | Another campaign is already booked for the requested period. Adjust the schedule and resubmit. |
| **Brand Policy** | Does not comply with the publisher's brand policy. |
| **Not Completed Within Deadline** | Review was not completed within the designated deadline. |
***
## Creative Rejection Reasons
### Legal and Regulatory
| Reason | Description |
| - | - |
| **Current Law Violation** | Content that violates current laws and major recommendations. |
| **Industry Policy Non-compliance** | Regulated industries require review numbers and mandatory text. |
| **Prohibited Industry** | This industry is not allowed to advertise. |
### User Protection
| Reason | Description |
| - | - |
| **Usability Infringement** | Content that harms user experience. |
| **Youth Protection** | Content harmful to minors. |
### Content Quality
| Reason | Description |
| - | - |
| **False/Exaggerated/Deceptive** | Contains false, exaggerated, or deceptive content. |
| **Comparison/Disparagement** | Unfairly compares or disparages competitors. |
### Inappropriate Content
| Reason | Description |
| - | - |
| **Obscene/Suggestive** | Obscene or suggestive content. |
| **Violence/Hate** | Contains violence, hate, fear, profanity, etc. |
| **Social Sentiment Violation** | Content that goes against general social sentiment. |
### Rights Infringement
| Reason | Description |
| - | - |
| **Third-Party Rights Infringement** | Infringes on copyrights, portrait rights, or other third-party rights. |
### Technical Issues
| Reason | Description |
| - | - |
| **Creative Conversion Failure** | An error occurred while processing the creative file. |
***
## Prohibited Content
The following content cannot be used in ads.
### Absolutely Prohibited
* Adult/pornographic content
* Violence, gore, cruel content
* Hate speech, discrimination, harassment
* Copyright/intellectual property infringement
* False health or financial claims
* Unsubstantiated product claims
* Content exploiting or harmful to minors
### Regulated Industries (Conditional)
The following industries may have additional requirements:
| Industry | Requirements |
| - | - |
| **Alcohol** | Age restriction, warning text |
| **Tobacco** | Restricted per regulations |
| **Gambling** | License verification, age restriction |
| **Pharmaceuticals** | Review number, mandatory text |
| **Health Supplements** | Review number, mandatory text |
| **Financial Services** | Regulatory compliance verification |
| **Diet/Weight Loss** | No false claims |
| **Cryptocurrency** | Regulatory compliance verification |
***
## Technical Requirements
### Image Creatives
| Item | Requirement |
| - | - |
| **Format** | JPG, PNG, WebP, GIF |
| **Max Size** | 10 MB |
| **Resolution** | 3× the ad specification |
| **Aspect Ratio** | Same as ad specification (1% tolerance) |
### Video Creatives
| Item | Requirement |
| - | - |
| **Format** | MP4 (recommended), MOV, WebM, MKV, AVI |
| **Max Size** | 1 GB |
| **Duration** | 3 seconds or more, under 30 seconds |
| **Resolution** | 1080p or higher |
| **Aspect Ratio** | 16:9 or 9:16 |
### Destination URL
| Item | Requirement |
| - | - |
| **Validity** | Must be a working URL |
| **Security** | HTTPS recommended |
| **Content** | Must be consistent with ad content |
***
## Industry-Specific Additional Requirements
Ads for regulated industries must comply with the following.
### Review Numbers
The following industries must display review numbers on creatives:
* Pharmaceuticals
* Health supplements
* Medical devices
* Financial products
### Mandatory Text
Must include legally required warning text or disclaimers.
***
## Responding to Rejection
### Check Rejection Reason
1. Check the rejection reason on the campaign or creative detail screen.
2. The rejection reason is also sent via email.
### Modify and Resubmit
1. Modify the creative or campaign according to the rejection reason.
2. Click the **Save** button to resubmit.
3. A new review will be conducted.
### Inquiries
Contact the publisher if you have questions about the rejection reason.
***
## Policy Compliance Tips
* Ad content and landing page content must be consistent.
* Avoid exaggerated or unverified claims.
* When using copyrighted images or music, secure the rights.
* For regulated industries, prepare required review numbers and text in advance.
* Verify technical specifications and prepare high-quality creatives.
***
## Next Steps
For publishers, learn how to review and manage campaigns.
***
## Related Documents
* [Registering Creatives](/campaign/creative)
* [Operating Campaigns](/campaign/operation)
# Quickstart: Book Your First Campaign
Source: https://docs.adrop.io/campaign/quickstart
This guide walks you through the process from creating an ad account to submitting a campaign.
> ⏱️ Estimated time: About 10 minutes
***
## Before You Start
* Ad Platform link (provided by publisher)
* Business information (country, business name, tax ID)
* Payment method (card information)
* Creative files (images or videos)
To support global business and comply with international tax standards, entering business information is required. Once saved, it can be reused for new ad accounts.
***
## Step-by-Step Guide
Access via the link provided by the publisher.
1. Open the Ad Platform URL in your browser. (e.g., `https://{publisher}.adrop.io`)
2. Log in or sign up.
1. Click the **Create Ad Account** button.
2. Enter account name and contact information.
3. Click the **Create** button.
1. Click the **Book Campaign** button.
2. Select an ad product.
3. Set period and budget.
4. Choose targeting. (Optional)
5. Click the **Next** button.
1. Review booking details.
2. Select a payment profile, or add one via **Add Payment Profile** if none exists.
3. Agree to terms and click the **Submit** button.
1. Click the **Register Creative** button in the campaign details screen.
2. Upload creative files.
3. Enter landing URL.
4. Click the **Save** button.
***
## Completion
Once the campaign is submitted, publisher review begins. You can check the review status on the Ad Platform, and email notifications will also be sent.
***
## Next Steps
**Learn More**
Learn how to manage payment profiles and members.
**Next Quickstart**
Book campaigns directly on the Ad Platform provided by the publisher.
If you're a publisher, review and manage campaigns.
***
## FAQ
Reviews are typically completed within 1-3 business days. For urgent bookings (within 7 days of start date), contact the publisher directly for expedited review.
Yes, you can submit the campaign first and register creatives after approval. However, both the campaign and creatives must be approved for ads to be displayed.
For card payments, a full refund is issued. You can review the rejection reason, make corrections, and resubmit.
# Splash Ad Guide
Source: https://docs.adrop.io/campaign/splash-guide
Learn how to book splash ad products that display full-screen when the app launches.
***
## What are Splash Ads?
**Splash ads** are full-screen ads that appear first when users launch the app. They are displayed during app loading, achieving high visibility. As display-only ads without click functionality, they are effective for brand awareness.
### Features
* **Full-screen display**: Shown across the entire screen when app launches
* **High visibility**: Ads users are guaranteed to see
* **Display only**: No click functionality (no destination URL needed)
* **Ideal for branding**: Effective for brand launches, seasonal campaigns
### Use Cases
* New product launch promotions
* Brand awareness campaigns
* Seasonal promotions (holidays, Black Friday, etc.)
* Major event announcements
***
## Creatives
Splash ads support images only. Videos and GIFs cannot be used.
### Image Specifications
| Item | Specification |
| - | - |
| **Format** | JPG, PNG, WebP |
| **Aspect Ratio** | 4:3 (based on 360×270) |
| **Min Resolution** | 1080×810 (3×) |
| **Max Size** | 10 MB |
### Registration Steps
1. Click the **Add Creative** button on the campaign detail screen.
2. Upload an image that meets the specifications.
3. Enter the creative name.
4. Click the **Submit** button.
Since splash ads don't have click functionality, you don't need to enter a destination URL or CTA button.
### Dark Mode
If the publisher supports dark mode, you can register separate images for dark mode.
### Display Duration
The time before the ad automatically closes is set by the publisher. Typically between 0.5 to 3 seconds.
***
## 72-Hour Advance Lock Rule
Splash ads are pre-downloaded to user devices and displayed immediately when the app launches. For this reason, creatives and schedules must be finalized **72 hours before** campaign start.
### Why Advance Lock is Needed
1. Creatives are cached on user devices starting 72 hours before campaign start.
2. Cached information enables immediate ad display when app launches.
3. Ensures stable delivery regardless of network conditions.
### Important Notes for Changes Within 72 Hours
Changes made within 72 hours of campaign start may cause the following issues:
| Change Type | Impact |
| - | - |
| **Creative change** | Some users may continue seeing the old creative |
| **Creative pause** | Some users may continue seeing the paused creative until the device cache refreshes (applies to mid-campaign pauses as well) |
| **Schedule change/cancellation** | Some users may see ads according to the old schedule |
New information is only reflected after users restart the app and the device cache refreshes. Both old and new creative impressions are billable, and penalty fees follow publisher policies.
We recommend getting creatives approved at least 72 hours before campaign start.
***
## Performance Reports
Since splash ads don't have click functionality, click-related metrics are not displayed in reports.
* Only impressions are tracked
* Clicks: Always 0
* CTR: Not applicable
***
## Next Steps
Review ad review criteria and policies.
***
## FAQ
No, splash ads are display-only ads. Since there's no click functionality, you don't set a destination URL.
Splash ads are pre-downloaded to user devices for immediate display when the app launches. Locking 72 hours in advance ensures stable delivery to all users.
Some users may continue seeing the old creative. New creatives are only reflected after users restart the app. Both old and new creative impressions are billable.
No. Splash ads are prefetched and cached on each user's device, so a paused creative may continue to be shown from the device cache until the next refresh cycle completes. This applies to pauses made mid-campaign as well — plan pauses with a buffer for the cache to roll over.
# Advertiser Quickstart
Source: https://docs.adrop.io/getting-started/advertiser-quickstart
A step-by-step guide for Advertisers, from Campaign Booking to performance analysis.
Total estimated time: approximately 13 minutes
Create an advertising account and book your first Campaign.
Check and analyze Campaign performance in real-time.
***
## Before You Begin
To run ads, you need the **Ad Platform link** provided by the Publisher. If you don't have the link, contact the Publisher you want to advertise with.
Ad Platform link example: `https://{publisher}.adrop.io`
***
## Next Steps
Request the Ad Platform link from the Publisher and get started
# Ad Monetization, From the First Step to Success
Source: https://docs.adrop.io/getting-started/index
Ad Control is a SaaS-based all-in-one ad monetization solution for IT platform companies. Deploy your own Ad Platform instantly without complex development, enabling both direct deals and programmatic ads monetization in a single unified system.
## Core Value
Preserve your platform service's value while delivering the optimal revenue model for business sustainability.
Prioritize high-CPM direct deals, with [**Backfill ads**](/ad-unit/backfill) automatically filling remaining inventory to optimize revenue.
Manage everything from Ad Products planning to reporting through a single Console, without requiring development resources.
Instantly deploy and test various ad formats including banner, video, and native ads to match market trends.
### Key Features
Launch a Publisher Console for operations and an Ad Platform where Advertisers can directly book Campaigns, both branded with your service logo.
High-performance ad servers delivered as SaaS, handling large-scale traffic with guaranteed 99.9% uptime.
One-stop management for Campaign creation, Creative review, report analysis, and all operational needs.
Integrate first-party data to support precise Audience Targeting by gender, age, interests, and more.
Currently deployed by over 80 partners. From large platforms with 10M+ MAU to vertical startups, Ad Control is a proven and stable monetization solution regardless of scale.
***
## Problems We Solve
Network ads like AdMob and AdSense rarely exceed \$1 CPM. While direct deals can increase rates, infrastructure costs are burdensome.
**→ Ad Control provides direct deal infrastructure at server-cost levels.** Prioritize high-CPM direct deals, then automatically monetize empty inventory with Backfill ads.
Even at large companies, core service development comes first. When asked "Can we do video ads?", internal dev teams quote months of work.
**→ Ad Control lets you deploy new ad formats with just a few clicks.** Respond instantly to market trends with banner, video, native, and more.
Ad monetization expertise is scarce, often forcing marketers to suddenly become ad managers. Time spent on repetitive tasks means no time for valuable work.
**→ Ad Control automates Campaign management, reporting, and Settlement.** Let your team focus on sales and strategy.
Building in-house means cost burden and losing focus on core business as you cater to Advertiser demands.
**→ Ad Control provides enterprise-grade infrastructure as SaaS.** Start immediately without build burden.
Even when you find good media, the process from inquiry to contract is complex.
**→ Check Ad Products directly on the Publisher's Ad Platform and book self-serve.**
Must wait for Excel reports or manually request performance data.
**→ Check and download real-time performance reports directly.** Systematized systems increase repurchase rates.
***
## How It Works
Create your platform and configure Ad Products.
* Generate an **Ad Platform** branded with your logo
* Register **Ad Units** (inventory) where ads will be displayed
* Bundle Ad Units to create **Ad Products** and set prices
Advertisers book directly, or Publishers register on their behalf.
* Advertisers view products on the platform and **book self-serve**
* Or Publishers can **register Campaigns directly**
* Empty inventory is automatically monetized with **Backfill ads**
Reporting and Settlement are handled automatically.
* Campaign performance is tracked via **real-time reports**
* Revenue is **automatically settled monthly** and paid to registered accounts
***
## Next Steps
From Ad Platform deployment to first Revenue (approx. 27 min)
From Campaign Booking to performance analysis (approx. 13 min)
# Publisher Quickstart
Source: https://docs.adrop.io/getting-started/publisher-quickstart
A step-by-step guide for Publishers, from Ad Platform deployment to first Revenue.
Total estimated time: approximately 27 minutes
Deploy your own Ad Platform branded with your logo.
Register your app and create Ad Units (inventory) where ads will be displayed.
Bundle Ad Units to create sellable Ad Products.
Integrate first-party data to configure precise Targeting products.
Review and approve Campaigns submitted by Advertisers.
Upgrade to a paid plan and begin official operations.
***
## Next Steps
Start from the first step
# Campaign Details
Source: https://docs.adrop.io/management/detail
Learn about items you can view and configure on the campaign detail screen.
***
## Campaign Overview
Click a campaign to open the detail screen. View campaign settings and operational status at a glance.
### Campaign Information
| Item | Description |
| - | - |
| **Campaign ID** | Auto-assigned unique identifier. Click to copy |
| **Ad Product** | Product name, discount rate, expected CTR |
| **Billing Type** | Billing method (CPM, CPC, etc.) and unit price |
| **Budget** | Total campaign budget |
| **Period** | Start date \~ End date (including time zone) |
| **Frequency Cap** | Daily impression frequency per user |
| **Daily Click Quota** | Auto/manual setting |
| **Country** | Ad display country |
| **Advertiser** | Advertiser name, website URL |
| **Contract Contact** | Contact name, email |
| **Payment Method** | Card payment (prepaid) or Manual payment (postpaid), receipt link |
| **Business Info** | Business name, TAX ID, billing email |
| **Targeting** | Include/exclude targeting options, premium rates by targeting |
### Budget Spending
Displayed for campaigns in Live or Paused status.
* **Spent Amount**: Budget spent so far and total budget
* **Progress**: Circular progress bar comparing actual spending (black) vs. expected spending (gray)
* **Status Interpretation**: Spending status compared to expectations (e.g., "Spending faster than expected")
### Performance
| Metric | Description |
| - | - |
| **Total Impressions** | Cumulative impressions. CPM campaigns show achievement rate vs. target |
| **Daily Impressions** | Today's impressions and achievement rate vs. daily quota |
| **Total Clicks** | Cumulative clicks. CPC campaigns show achievement rate vs. target |
| **Daily Clicks** | Today's clicks and achievement rate vs. daily quota |
Clicking the **Performance Report** button opens the Report menu in a new window with the campaign filter pre-applied, so you can analyze daily performance with date ranges, segments, and additional filters. **Statement** button also appears for cancelled or completed campaigns.
***
## Ad Creatives
View the list of creatives registered to the campaign and performance of each creative. Creatives pending review are displayed separately at the top and can be reviewed directly by clicking.
A warning banner appears if required creatives are not registered.
### Creative List
| Item | Description |
| - | - |
| **Status** | Under Review, Approved, Rejected, Paused |
| **Creative Name** | Name of the creative |
| **Placement** | Ad creative set name |
| **Format** | Format and size (e.g., Display 300x250) |
| **Impressions** | Impressions per creative |
| **Clicks** | Clicks per creative |
| **CTR** | Click-through rate per creative |
### Creative Preview
Click a creative to open the preview screen. View images at actual size and videos with playback preview. Light/dark mode toggle is also available.
If a video creative is encoding, preview will be available after completion.
***
## Frequency Cap
Limits how often ads from the same campaign are shown to the same user. For example, setting frequency to 50 means a user sees ads from that campaign a maximum of 50 times per day.
Can be configured for campaigns in Booked, Live, or Paused status.
### Why is this needed?
Seeing the same ad too often causes users to feel fatigue, and at worst, develop negative perceptions of the brand. This is called **Ad Fatigue**. Frequency caps find a balance between preventing this while still securing sufficient exposure.
### Usage Guide
| Situation | Recommended Frequency | Reason |
| - | - | - |
| **Branding campaigns** | Low (3-10 times) | Brand awareness forms with fewer impressions; overexposure has negative effects |
| **Promotions/Events** | Medium (10-30 times) | Urgent messages benefit from repeated exposure |
| **Retargeting** | High (30-50 times) | More exposure to already-interested users improves conversions |
Too low a frequency may prevent full budget spending; too high concentrates ads on few users. Set based on campaign goals and DAU.
### How to Set
1. Click **Frequency Cap** on the campaign detail screen.
2. Enter your desired frequency. (Default: 50 times/day)
3. Click the **Save** button.
***
## Daily Quota
Set daily impression (CPM) or click (CPC) targets for the campaign. Can be configured for campaigns in Booked, Live, or Paused status.
### Why is this needed?
Ad campaigns typically need to achieve target volumes within a specific period. Daily quotas adjust **pacing** to achieve these targets evenly throughout the period.
Without pacing, budget may be rapidly consumed during high-traffic early periods, leaving no ad delivery later. Conversely, being too conservative may result in missing targets by the end of the period.
### Auto vs Manual
| Mode | Behavior | Suitable for |
| - | - | - |
| **Auto (Recommended)** | Recalculates daily at midnight based on remaining target; auto-optimizes to improve achievement rate | Most campaigns |
| **Manual** | Fixes daily quota to specified value | Concentrating delivery on specific days or adjusting to event schedules |
Setting daily quota too low may prevent reaching targets within the campaign period. Underperformance may cause disputes with advertisers, so we recommend Auto unless there's a specific reason.
### How to Set
1. Click **Daily Impression Quota** (CPM) or **Daily Click Quota** (CPC) on the campaign detail screen.
2. Select **Auto** or **Manual**.
3. If Manual, enter your desired daily quota.
4. Click the **Save** button.
Even with manual settings, you cannot enter values exceeding the total target.
***
## Next Steps
Learn how to modify, pause, or cancel campaigns per advertiser requests.
***
## FAQ
Calculated in real-time, with up to a few minutes delay due to data processing.
Yes, but use caution. Setting too low manually may prevent reaching targets within the period. We recommend Auto unless there's a specific reason.
No. Modified creatives require re-review. Delivery starts after review approval.
This means creatives for units designated as required in the ad product are missing. Ask the advertiser to register creatives for those units.
# Publisher Direct Booking
Source: https://docs.adrop.io/management/direct-booking
Learn how publishers can create campaigns on behalf of advertisers.
Typically, advertisers create campaigns and complete payment through self-service in the console. Use publisher direct booking only when pre-agreed with the advertiser.
***
## What is Direct Booking?
A feature where publishers create campaigns on behalf of advertisers. Use in the following cases:
* When advertisers have difficulty using the console
* When contracts are completed offline and publishers need to register directly
* When running internal promotional campaigns
### Limitations
| Item | Details |
| - | - |
| **Payment Method** | Manual payment only (card payment not available) |
| **Start Date** | Cannot be set to a past date |
| **Review** | Campaigns created by publishers are auto-approved |
Creating campaigns without advertiser consent may cause disputes. Always confirm with the advertiser beforehand.
***
## Creating a Campaign
### Getting Started
1. Click **Bookings** in the left menu.
2. Click the **Create Campaign** button.
### Step 1: Ad Product & Schedule
In this step, complete ad account selection, product selection, schedule setting, and targeting all at once.
**Selecting Ad Account**
First, select an ad account. The advertiser name and website are automatically populated when an ad account is selected.
* **Select Existing Account**: Choose from the list of registered ad accounts
* **Create New Account**: Create new if no advertiser account exists
Fields for creating a new account:
| Item | Description |
| - | - |
| **Advertiser Name** | Advertiser name (required) |
| **Website** | Advertiser website URL (optional) |
| **Country** | Country for accounting purposes |
| **Time Zone** | Time zone applied to campaign schedule |
| **Language** | Console display language |
**Selecting Ad Product**
After selecting the ad account, select an ad product.
**Setting Schedule**
Set the campaign start and end dates.
**Setting Targeting** (Optional)
Configure targeting if needed.
* Select categories or groups to include
* Select categories or groups to exclude
### Step 2: Campaign Info
| Item | Description |
| - | - |
| **Campaign Title** | Name to distinguish the campaign |
| **Budget** | Total campaign budget |
| **Advertiser Name** | Auto-populated from ad account |
| **Advertiser Website** | Auto-populated from ad account |
### Step 3: Payment & Business Info
| Item | Description |
| - | - |
| **Contact Name** | Contract contact person name |
| **Contact Email** | Email address for notifications |
| **Payment Method** | Manual payment (direct booking doesn't support card payment) |
### Step 4: Final Confirmation
1. Review the entered information.
2. Check **Terms Agreement**.
3. Click the **Book** button.
Campaigns created by publishers immediately become Booked status without separate review.
***
## Registering Creatives
After creating a campaign, creatives must be registered for ads to display.
### Registration Steps
1. Navigate to the created campaign's detail screen.
2. Click the **Add Creative** button in the **Ad Creatives** section.
3. Enter creative information.
### Creative Info
| Item | Description |
| - | - |
| **Creative Name** | Name to distinguish the creative |
| **Creative Set** | Select the creative set where ads will display |
| **Creative File** | Upload image or video |
| **Destination URL** | URL to navigate to when creative is clicked |
| **Target Browser** | Default browser or in-app browser |
### Creative Specifications
Required creative specifications vary based on the creative sets configured in the ad product. Check required specifications on the creative registration screen.
Recommended image resolution is 3× the display size. (e.g., 300x250dp unit → 900x750px image)
### Creative Review
Creatives registered by publishers also require review. You can approve directly from the campaign detail screen.
***
## Next Steps
Review operation-related settings like payment methods and advertiser permissions.
***
## FAQ
Yes. Directly booked campaigns are displayed in the corresponding ad account. Advertisers can check campaign status and performance when they log into the console.
Card payment requires advertisers to enter their own payment information and consent. Since publishers cannot process card payments on behalf of advertisers, direct booking only supports manual payment (postpaid).
# Campaign Notifications
Source: https://docs.adrop.io/management/notifications
Learn about notifications sent during campaign operations.
***
## Campaign Start/End Notifications
Notifications are sent according to the campaign schedule.
| Timing | Recipients | Content |
| - | - | - |
| **1 hour before start** | Publisher, Advertiser | Campaign is about to start |
| **1 hour before end** | Publisher, Advertiser | Campaign is about to end |
***
## Creative Action Required Notifications
Starting 7 days before campaign start, notifications are sent based on creative status.
| Situation | Recipients | Content |
| - | - | - |
| **Creative not registered** | Advertiser | Please register creatives |
| **Creative rejected** | Advertiser | Creative was rejected. Please modify and resubmit |
| **Creative pending review** | Publisher | There are creatives awaiting review |
***
## Underperformance Notifications
If more than 24 hours have passed since campaign start and performance is below daily targets, publishers receive a notification.
| Condition | Recipients | Content |
| - | - | - |
| **Below daily target** | Publisher | Campaign performance is below target |
***
## Review Result Notifications
Campaign and creative review results are sent to advertisers.
| Subject | Notification Triggers |
| - | - |
| **Campaign** | Review approved, Review rejected |
| **Creative** | Review approved, Review rejected |
***
## Notification Settings
You can configure which notifications to receive from the profile menu (top right) > **My Notifications**. Enable or disable each notification type individually.
***
## Next Steps
Learn how to view and analyze campaign performance.
***
## FAQ
Yes. From the profile menu (top right) > **My Notifications**, you can enable or disable each notification type individually.
Notifications are sent to the registered email. For publishers, notifications go to the admin email registered in the console. For advertisers, notifications go to the campaign contact email.
If more than 24 hours have passed since campaign start, actual performance is compared to daily targets, and notifications are sent when performance falls short.
# What is Campaign Management?
Source: https://docs.adrop.io/management/overview
A guide on how to review and operate campaigns booked by advertisers.
***
## What is Campaign Management?
When advertisers book campaigns, publishers review them and manage approved campaigns to ensure they operate smoothly.
Campaign management consists of three main stages:
1. **Review**: Review and approve/reject campaigns and creatives submitted by advertisers.
2. **Operation**: Monitor ongoing campaigns and modify, pause, or cancel them as needed.
3. **Settings**: Configure operational policies such as advertiser permissions, payment methods, and terms.
***
## Campaign Overview
You can view all campaigns in the Booking Management menu. Filter by campaign status, billing type, Ad Products, and date range to quickly find the campaigns you need.
### View Modes
**List View** displays campaigns in a table format. Status, campaign name, spent/budget, period, and performance are shown at a glance.
**Timeline View** represents campaigns as bars in a calendar format. This is useful for comparing campaigns running during the same period.
### Campaign Status
Campaigns progress through the following flow:
Advertiser is creating the campaign.
Waiting for publisher's review.
Approved and waiting for the start date.
Campaign is running and ads are being served.
Campaign has ended.
**Exception Status**
* **Rejected**: Rejected during review
* **Paused**: Temporarily paused while active
* **Cancelled**: Campaign has been cancelled
"Draft" status is for campaigns not yet submitted by advertisers and will not appear in the console.
***
## Table of Contents
1. ⚡ [Quickstart: Reviewing Campaigns](/management/quickstart)
2. [Reviewing Campaigns](/management/review)
3. [Campaign Detail Information](/management/detail)
4. [Supporting Campaigns](/management/support)
5. [Campaign Notifications](/management/notifications)
6. [Campaign Reports](/management/reports)
7. [Publisher Direct Booking](/management/direct-booking)
8. [Operation Settings](/management/settings)
# Quickstart: Reviewing Campaigns
Source: https://docs.adrop.io/management/quickstart
A guide on how to review and approve campaigns submitted by advertisers.
> ⏱️ Time required: approximately 3 minutes
***
## Before You Begin
* A campaign pending review
Publishers can also create campaigns directly. See [Publisher Direct Booking](/management/direct-booking) for details.
***
## Step-by-Step Guide
1. Click **Booking Management** in the left menu.
2. Select **Submitted** status in the top filter.
3. A list of campaigns requiring review will be displayed.
1. Click on a campaign to go to the detail screen.
2. Check the following items:
* Ad Products
* Campaign period
* Budget
* Targeting settings
**To approve**
1. Click the **Approve Campaign** button.
2. Campaign status changes to **Booked**.
3. It will automatically transition to **Live** status on the start date.
**To reject**
1. Click the **Reject Campaign** button.
2. Select a rejection reason and write a message.
3. Click the **Reject** button.
4. A rejection notification will be sent to the advertiser.
After approving the campaign, you must also review the creatives.
1. Check the **Creatives** section in the campaign detail screen.
2. Click on each creative to check the image/video and destination URL.
3. Click the **Approve** or **Reject** button.
Even if the campaign is approved, ads will not be served if there are no approved creatives.
***
## Completion
Once both the campaign and creatives are approved, ad serving will begin on the start date.
***
## Next Steps
**Learn More**
Check review deadlines and rejection reasons in detail.
**Go to Next Quickstart**
Learn how to set up settlement.
***
## FAQ
Advertisers can upload creatives even after campaign approval. You'll receive a review notification when creatives are registered. Starting 7 days before the campaign start date, the advertiser will receive notifications about missing creatives.
The default review deadline is 7 days, but we recommend reviewing as quickly as possible. If not reviewed within the deadline, the advertiser will receive a delay notification.
When an advertiser modifies a campaign, the status changes back to **Submitted**. You can check it using the Submitted filter in Booking Management.
# Campaign Reports
Source: https://docs.adrop.io/management/reports
Learn how to view and analyze campaign performance.
For basic report usage, see [Analytics & Reports](/campaign/analytics). This document covers publisher-only features.
***
## Advertiser Report Settings
Configure the scope of reports provided to advertisers.
Click the Report Settings button in the top right of the report screen to open the settings modal.
| Item | Description |
| - | - |
| **Campaign** | Show Campaign tab (enabled by default, cannot be changed) |
| **Creative** | Show Creative tab (enabled by default, cannot be changed) |
| **Ad Unit** | Show Unit tab |
| **Ad Product** | Show Product tab |
| **Date** | Show daily analysis (enabled by default, cannot be changed) |
| **Hour** | Show hourly analysis |
* **Enabled**: Advertisers can use that tab or time-based analysis
* **Disabled**: That feature is hidden in advertiser reports
***
## Publisher-Only Columns
The following columns are only displayed in publisher reports and are not visible to advertisers.
| Column | Description |
| - | - |
| **Private Campaign** | Whether campaign was booked with private product |
| **Ad Note Campaign** | Whether campaign was booked through Ad Note |
| **Commission** | Agency commission amount |
| **Settlement** | Final settlement amount (budget - commission - refund) |
***
## Next Steps
Learn how to create campaigns directly without an advertiser.
Query campaign performance and backfill revenue data directly via API.
***
## FAQ
No. The Backfill tab is publisher-only. It is not displayed in advertiser reports.
Immediately. After changing settings, advertisers see the new settings when they view reports.
It's the final settlement amount after subtracting agency commission and refund amounts from the budget.
# Reviewing Campaigns
Source: https://docs.adrop.io/management/review
A guide on how to review and approve/reject campaigns and creatives submitted by advertisers.
For ads to be served, both campaigns and creatives must be approved. Review both campaigns and creatives together, and request advertisers to upload creatives if they are missing.
***
## Viewing Campaign List
Click **Booking Management** in the left menu to view the campaign list.
### View Modes
Select the view mode from the tabs at the top of the screen.
* **List View**: Displays campaigns in a table format. Status, campaign name, spent/budget, period, and performance are shown at a glance.
* **Timeline View**: Visualizes campaign schedules in a calendar format. Useful for comparing campaigns running during the same period.
### Filters
Use the top filters to quickly find the campaigns you need.
| Filter | Description |
| - | - |
| **Search** | Search by campaign name |
| **Period** | Select by month or date range |
| **Status** | Filter by status such as Submitted, Booked, Live |
| **Billing Type** | Filter by billing method such as CPM, CPC, CPP |
| **Ad Products** | Filter by product |
### Notification Badges
Check the following badges in the campaign list:
| Badge | Meaning |
| - | - |
| **Campaign Review Required** | A submitted campaign is pending review. |
| **Upcoming Booking** | Campaign with an approaching start date. Review as a priority. |
| **Creative Review Required** | An approved campaign has creatives pending review. |
***
## Campaign Review
For a detailed explanation of campaign status, see [Campaign Operation](/campaign/operation).
### Check Campaigns Pending Review
1. Click **Booking Management** in the left menu.
2. Select **Submitted** status in the top filter.
3. Click on a campaign to open the detail screen.
### Campaign Detail Screen
The campaign detail screen displays the following information:
**Campaign Information**
| Item | Description |
| - | - |
| **Ad Products** | Selected product, discount rate, and expected CTR |
| **Billing Type** | Billing method (CPM, CPC, etc.) and unit price |
| **Budget** | Total campaign budget |
| **Campaign Period** | Start and end dates (including timezone) |
| **Country** | Country for ad serving |
| **Advertiser** | Advertiser name and URL |
| **Contract Manager** | Manager name and email |
| **Payment Method** | Card payment (prepaid) or manual payment (postpaid) |
| **Business Information** | Business name, TAX ID, email |
**Targeting**
Check the configured targeting conditions. Targeting options and required status are displayed.
### Approval Process
1. Click the **Approve Campaign** button in the review panel at the top of the campaign detail screen.
2. Click the **Approve** button in the confirmation modal.
3. Campaign status changes to **Booked**.
4. It will automatically transition to **Live** status on the start date.
For guaranteed period (CPP) products, approval will fail if there are no available booking slots for that period. In this case, reject the campaign and request the advertiser to adjust the schedule.
### Rejection Process
1. Click the **Reject Campaign** button in the review panel at the top of the campaign detail screen.
2. Select a rejection reason.
3. Click the **Reject** button.
4. A rejection notification will be sent to the advertiser.
### Rejection Reasons
| Reason | Description |
| - | - |
| **Already Booked Period** | Another campaign is already booked for the requested period. |
| **Brand Policy** | Does not comply with the publisher's brand policy. |
### Refunds on Rejection
Card payment campaigns are automatically refunded in full when rejected. Manual payment campaigns do not require refunds since payment has not been made yet.
### Review Deadline
Campaigns must be reviewed by whichever comes first:
* Within **72 hours** after campaign submission
* **Before the campaign start date**
If the review deadline is exceeded, the campaign will be automatically rejected and the advertiser will be notified.
**Same-day booking campaigns**: If submitted after the campaign start date, it will be automatically rejected if not approved within **24 hours** of start.
***
## Creative Review
Even during campaign operation, if an advertiser modifies a creative, re-review is required. Modified creatives will not be served until approved.
### Check Creatives Pending Review
Click on campaigns with the **Creative Review Required** badge in the campaign list. A list of creatives pending review will be displayed at the top of the campaign detail screen.
### Review Creatives
1. Click on a creative pending review to open the creative detail screen.
2. Check the following items:
* Creative image/video preview
* Destination URL (where users go when clicking the creative)
* Creative specifications (size, format)
### Approve/Reject Process
1. Click the **Approve** or **Reject** button in the creative detail screen.
2. Select a reason when rejecting.
3. The review result notification will be sent to the advertiser.
Rejected creatives can be resubmitted by the advertiser after modification.
### Creative Rejection Reasons
Select a reason when rejecting. Various reasons are provided such as violation of current laws, false/exaggerated advertising, obscene/provocative content, copyright infringement, prohibited industry advertising, etc. If no applicable reason exists, you can enter one manually.
***
## Next Steps
Check items in the campaign detail screen such as budget spending, performance, and daily quota.
***
## FAQ
It's useful when comparing campaigns running during the same period. Use it especially to check for CPP campaign slot conflicts or to understand campaign density during specific periods.
Another CPP campaign may already be booked for that period, leaving no slots available. Reject the campaign and request the advertiser to adjust the schedule.
The campaign will be automatically rejected and the advertiser will be notified. Card payment campaigns will be fully refunded.
The modified creative changes to **Under Review** status and will not be served until approved. Existing approved creatives will continue to be served.
# Operation Settings
Source: https://docs.adrop.io/management/settings
Learn about settings needed for campaign operations.
***
## Campaign Booking Terms
Set the terms advertisers agree to when booking campaigns.
### How to Set
1. Click the **Upload Terms** button.
2. Select a PDF file.
3. The uploaded terms are displayed on the advertiser's campaign booking screen.
Uploaded terms can be deleted or replaced with a new file at any time.
***
## Payment Methods
Configure which payment methods advertisers can use.
| Method | Description |
| - | - |
| **Card Payment** | Advertiser pays upfront by card. Unused budget is automatically refunded after campaign ends. |
| **Manual Payment** | Settle directly with publisher after campaign ends. |
You can enable both methods or just one.
### Manual Payment
| Setting | Behavior |
| - | - |
| **Available to All** | All advertisers can select manual payment. |
| **Allowlist Only** | Only advertisers in the **allowlist** can select manual payment. |
| **Not Available** | No advertisers can select manual payment. |
### **Allowlist Management**
1. Select '**Allowlist Only**' for manual payment.
2. Click the **Allowlist** button on the right.
3. Add advertiser emails or domains to allow manual payment and click **Save Changes**.
Campaigns created directly by publishers only support manual payment.
***
## Private Ad Products
### **How to Set**
Check **Private** in the **Product Info** section when creating an ad product.
### **Exposing Private Products to Advertisers**
To expose private products to specific advertisers, you need to set permissions in the **Private Product Access**.
1. Click **Management > Operations** in the left menu.
2. Add advertisers in the **Private Product Access** section.
3. Select the required **Access Permission** and click **Save Changes**.
***
## Platform Policy
### Creative Display Day & Time Settings
Allow advertisers to set display days and times per creative.
| Setting | Behavior |
| - | - |
| **Enabled** | Advertisers can set display schedules when registering creatives |
| **Disabled** | Creatives are always displayed during the campaign period |
### CPP Budget & Targeting Modification
Allow advertisers to directly set budget and targeting for CPP (Cost Per Period) products.
#### **How to Set**
1. Click **Management > Operations** in the left menu.
2. Click the **Allowlist** button in **Platform Policy** > **CPP Budget & Targeting Modification** section.
3. Add advertiser emails or domains to allow and click **Save Changes**.
***
## Next Steps
Check revenue settlement and infrastructure billing.
***
## FAQ
The configured permissions apply to all users with that domain. For example, adding `example.com` applies to `user@example.com`, `admin@example.com`, and all other users.
No. Private products can only be seen by advertisers who have been granted access permission for that product in the Private Product Access settings.
No. Changed terms only apply to newly booked campaigns afterward. Existing campaigns retain the terms agreed to at booking time.
# Campaign Support
Source: https://docs.adrop.io/management/support
Learn how to modify, pause, cancel, and duplicate campaigns and manage creatives per advertiser requests.
Advertisers cannot directly modify or cancel campaigns after approval. Handle according to this guide when advertisers make requests.
***
## Editing Campaigns
Advertisers may request changes to period or targeting for approved campaigns.
### Editable Items
| Status | Editable Items |
| - | - |
| **Booked** | Title, period, targeting |
| **Live** | Title, end date extension, targeting |
| **Paused** | Title, end date extension, targeting |
### Non-Editable Items
The following items cannot be changed after campaign approval.
* Ad product
* Budget
* Billing type
If budget or ad product changes are needed, guide the advertiser to book a new campaign.
### How to Edit
1. Click the menu (⋯) on the campaign detail screen.
2. Select **Edit**.
3. Modify the desired items.
4. Click the **Save** button.
***
## Pausing Campaigns
Advertisers may request to pause campaigns. Only Booked or Live campaigns can be paused.
### Pause Effects
* Ad delivery stops immediately.
* Budget spending stops.
* Campaign end date does not change.
The campaign will auto-end when the end date passes even while paused. Inform advertisers of this.
### How to Pause a Campaign
1. Click the menu (⋯) on the campaign detail screen.
2. Select **Pause**.
3. Enter the campaign name to confirm.
4. Click the **Pause** button.
### Pausing Individual Creatives
You can also stop delivery of specific creatives rather than the entire campaign.
1. Click a creative on the campaign detail screen.
2. Click the **Pause** button.
3. Only that creative stops delivery.
### How to Resume
1. Click the menu (⋯) on the paused campaign detail screen.
2. Select **Resume**.
3. Campaign returns to its previous status.
***
## Cancelling Campaigns
Advertisers may request to cancel approved campaigns. Campaigns in Booked, Live, or Paused status can be cancelled.
### Before Cancelling
* Cancelled campaigns cannot be restored.
* Card payment campaigns automatically refund unused budget.
### How to Cancel
1. Click the menu (⋯) on the campaign detail screen.
2. Select **Cancel**.
3. Review and enter the final settlement amount.
4. Enter the campaign name to confirm.
5. Click the **Cancel** button.
6. Cancellation notification is sent to the advertiser.
### Refund Processing
| Payment Method | Refund |
| - | - |
| **Card Payment** | Unused budget auto-refunded |
| **Manual Payment** | Unused amount excluded from settlement |
***
## Duplicating Campaigns
Quickly create a new campaign with the same settings as an existing one.
### How to Duplicate
1. Click the menu (⋯) on the campaign detail screen.
2. Select **Duplicate**.
3. A duplicated campaign is created in **Draft** status.
4. Set the budget and other required fields, then submit.
### What Gets Duplicated
| Item | Duplicated |
| - | - |
| **Ad Product** | Yes |
| **Targeting** | Yes (if still valid) |
| **Frequency Cap** | Yes |
| **Payment Type** | Yes |
| **Contact Info** | Yes |
| **Creatives** | Yes (valid creatives only, requires re-review) |
If categories or groups in the original campaign's targeting have been deleted/deactivated, that targeting will be reset. Always verify targeting settings after duplication.
### What Gets Reset
| Item | Value After Duplication |
| - | - |
| **Budget** | 0 (must be set) |
| **Start Date** | 7 days from current date |
| **End Date** | Start date + original campaign duration |
| **Title** | Original title + unique ID |
| **Status** | Draft |
| **Creative Status** | Under Review (requires re-review) |
### Creative Duplication Rules
* Rejected or deleted creatives are not duplicated.
* All duplicated creatives are set to **Under Review** status and require re-review.
* Creatives incompatible due to ad unit changes are excluded.
### Cannot Duplicate When
Campaigns cannot be duplicated in these cases:
* Ad product has been deleted
* Ad product's creative set is missing or needs modification
***
## Revoking Creative Approval
If issues are found with approved creatives, you can revoke approval.
1. Click a creative on the campaign detail screen.
2. Click the **Reject** button.
3. Select the rejection reason.
4. Creative status changes to **Rejected**.
5. Rejection notification is sent to the advertiser.
If no approved creatives remain in the campaign, ads will not display.
***
## Creative Destination Settings
Configure how the destination URL opens when a creative is clicked. Set on the creative registration or edit screen.
### Browser Options
| Option | Description |
| - | - |
| **Default Browser** | Opens in device's default browser (default) |
| **In-App Browser** | Opens in app's internal browser without leaving the app |
### Platform Behavior
| Platform | Default Browser | In-App Browser |
| - | - | - |
| **iOS** | Navigates to Safari or default app | Uses SFSafariViewController |
| **Android** | Uses Chrome Custom Tabs | Uses Chrome Custom Tabs |
| **Web** | Opens in new tab | Navigates in current tab |
Android uses Chrome Custom Tabs for both options, so behavior is identical. On iOS, selecting in-app browser keeps users in the app, reducing bounce rates.
### WebView Environment Notes
Even for native apps, if ads are displayed via WebView, additional publisher implementation is required for in-app browser behavior.
* **iOS**: Set `useInAppBrowser: true` during SDK initialization and implement WKNavigationDelegate
* **Android**: Implement `shouldOverrideUrlLoading` in WebViewClient and integrate Custom Tabs
Without a bridge implementation to intercept link clicks and open them in the in-app browser, pages will navigate within the WebView or open in an external browser regardless of settings.
### Deep Link Handling
If the destination URL is an app scheme (e.g., `kakao://`, `instagram://`), it navigates directly to that app regardless of in-app browser settings. In-app browser only supports HTTP/HTTPS URLs.
***
## Preview Creatives on Test Device
See how ads will look on actual devices. Register a test device to preview creatives on that device.
### Registering Test Devices
1. Click **Test Devices** from the profile menu (top right).
2. Click the **Add Device** button.
3. Enter the following information:
* **Device Name**: Name to identify in console (2-30 characters)
* **ADID**: Device's advertising identifier (UUID format)
4. Click the **Add** button.
### How to Find ADID
ADID (Advertising ID) is an advertising identifier assigned per device.
**Android**
1. Open the **Settings** app.
2. Navigate to **Security and Privacy** > **Other Privacy Settings**.
3. Select **Ads**.
4. View the **Advertising ID**.
Install [AAID - Find your Google Advert](https://play.google.com/store/apps/details?id=com.kyumpany.aaid) app to copy ADID to clipboard.
**iOS (IDFA)**
1. Open the **Settings** app.
2. Navigate to **Privacy & Security** > **Tracking**.
3. Verify app tracking permissions are enabled.
Install [Get My IDFA](https://apps.apple.com/app/get-my-idfa/id1580368827) app and allow tracking to copy IDFA to clipboard.
iOS requires user tracking consent per ATT (App Tracking Transparency) policy. If tracking is denied, the advertising identifier cannot be accessed.
### Running Preview
1. Click the **View on Device** button on the creative preview screen.
2. Select a registered test device.
3. Click the **Preview** button.
4. Launch the app on the selected device to see the creative.
### Supported Environments
Preview uses the device's advertising identifier (ADID/IDFA). The following SDK versions or higher are required:
| Platform | Minimum Version |
| - | - |
| **Android** | 1.3.1 |
| **iOS** | 1.3.0 |
| **Flutter** | 1.1.1 |
| **React Native** | 1.1.1 |
| **Web** | Any version (requires ADID) |
For unsupported versions, the preview button is disabled with a guidance message.
### Limitations
| Item | Details |
| - | - |
| **Valid Duration** | Must verify within 5 minutes of preview setup |
| **Device Registration** | Device ADID must be pre-registered |
| **App Launch** | Must launch the app fresh after preview setup for creative to display |
***
## Next Steps
Check notifications sent during campaign operations.
***
## FAQ
Budget cannot be changed for approved campaigns. If budget changes are needed, guide the advertiser to book a new campaign.
The campaign auto-ends. Pausing doesn't extend the end date, so extend the end date first if needed.
This happens when categories or groups in the original campaign's targeting were deleted/deactivated. Verify targeting settings after duplication.
Verify the ADID is correct, it's within 5 minutes of preview setup, and you launched the app fresh. The preview button is disabled for unsupported SDK versions.
# ads.txt Setup
Source: https://docs.adrop.io/sdk/ads_txt
Set up inventory authentication for backfill ads.
## Authorized Digital Sellers
**Authorized Digital Sellers** is an IAB standard to prevent ad inventory fraud. Through `ads.txt` for websites and `app-ads.txt` for apps, you prove to advertisers that "this inventory can be sold through Adrop."
If files are not properly configured, advertisers (DSPs) won't trust ad requests, which may result in **ads not being displayed or significantly reduced revenue**.
***
## Choose the Right File for Your Environment
The file you need to configure depends on **where ads are rendered**.
**Native Apps**
When ads are displayed in Android/iOS native UI
**WebView-based Apps / Websites**
When ads are displayed in WebView or browser
Even for mobile apps, if the app is built with WebView and ads are displayed in the WebView area, you need to configure **ads.txt**. The criterion is not whether the app is registered in the app store, but **where ads are actually rendered**.
***
## Configuration
### Setting Up for the First Time
If you don't have an existing `app-ads.txt`, setting up a redirect is the simplest option.
```
Redirect 301 /app-ads.txt https://adrop.io/app-ads.txt
```
### Already Using app-ads.txt
If you already have content from other ad networks, you cannot use a redirect. Visit the URL below, copy the content, and add it to your existing file.
```
https://adrop.io/app-ads.txt
```
Deleting existing content will break other ad networks. Make sure to keep existing content and **append** Adrop's content.
### Upload Location
Upload to the root path of the website domain registered in App Store/Play Store.
```
https://your-domain.com/app-ads.txt
```
### Setting Up for the First Time
If you don't have an existing `ads.txt`, setting up a redirect is the simplest option.
```
Redirect 301 /ads.txt https://adrop.io/ads.txt
```
### Already Using ads.txt
If you already have content from other ad networks, you cannot use a redirect. Visit the URL below, copy the content, and add it to your existing file.
```
https://adrop.io/ads.txt
```
Deleting existing content will break other ad networks. Make sure to keep existing content and **append** Adrop's content.
### Upload Location
Upload to the root path of your website domain.
```
https://your-domain.com/ads.txt
```
***
## Verification
### File Access Check
Visit the configured URL to verify the file is properly applied.
```
https://your-domain.com/ads.txt
https://your-domain.com/app-ads.txt
```
### robots.txt Crawling Permission
If `robots.txt` blocks crawlers from accessing `ads.txt` or `app-ads.txt`, ad networks cannot verify the files and **ads will not be served**.
Ensure your `robots.txt` allows crawling of these paths:
```txt robots.txt theme={null}
User-agent: *
Allow: /ads.txt
Allow: /app-ads.txt
```
### Verify Google Crawler Access
Even if the file appears correctly in your browser, ads won't serve if your server or CDN blocks Googlebot. Verify with the following commands:
```bash theme={null}
# 1. Check file accessibility
curl -iL https://your-domain.com/ads.txt
# 2. Check accessibility as Google crawler
curl -iL -A "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" https://your-domain.com/ads.txt
```
Both commands should return identical content. If the second command returns a different response, check your server/CDN configuration.
Proper txt file configuration for your environment is required for backfill ads to serve correctly. Without this setup or if crawling is blocked, ad requests may be rejected.
***
## Next Steps
Set up the SDK for your platform:
Android SDK integration guide
iOS SDK integration guide
React Native SDK integration guide
Flutter SDK integration guide
Web SDK integration guide
# Claude Code
Source: https://docs.adrop.io/sdk/ai/claude-code
Easily integrate the ad SDK with AI coding tools
## Introduction
[Claude Code](https://claude.ai/claude-code) is an AI coding assistant that runs in your terminal.
It can automatically implement SDK integration by referencing the Adrop Help Center URL.
Copy the sample prompts below and just replace **App Key** and **Unit ID** to get started.
***
## Getting Started
### 1. Install Claude Code
```bash theme={null}
npm install -g @anthropic-ai/claude-code
```
### 2. Run in your project folder
```bash theme={null}
cd your-project
claude
```
### 3. Enter a prompt
Copy and use the sample prompts below.
***
## Sample Prompts
### Banner Ad
```
Refer to https://docs.adrop.io/sdk/android/banner
Add a banner ad at the bottom of MainActivity.
App Key: YOUR_APP_KEY
Unit ID: YOUR_UNIT_ID
```
### Native Ad
```
Refer to https://docs.adrop.io/sdk/android/native
Add native ads between RecyclerView feed items.
App Key: YOUR_APP_KEY
Unit ID: YOUR_UNIT_ID
```
### Interstitial Ad
```
Refer to https://docs.adrop.io/sdk/android/interstitial
Show an interstitial ad during screen transitions.
App Key: YOUR_APP_KEY
Unit ID: YOUR_UNIT_ID
```
### Rewarded Ad
```
Refer to https://docs.adrop.io/sdk/android/rewarded
Show a rewarded ad when the reward button is clicked, and grant 100 coins on completion.
App Key: YOUR_APP_KEY
Unit ID: YOUR_UNIT_ID
```
### Banner Ad
```
Refer to https://docs.adrop.io/sdk/ios/banner
Add a banner ad at the bottom of ViewController.
App Key: YOUR_APP_KEY
Unit ID: YOUR_UNIT_ID
```
### Native Ad
```
Refer to https://docs.adrop.io/sdk/ios/native
Add native ads between TableView feed items.
App Key: YOUR_APP_KEY
Unit ID: YOUR_UNIT_ID
```
### Interstitial Ad
```
Refer to https://docs.adrop.io/sdk/ios/interstitial
Show an interstitial ad during screen transitions.
App Key: YOUR_APP_KEY
Unit ID: YOUR_UNIT_ID
```
### Rewarded Ad
```
Refer to https://docs.adrop.io/sdk/ios/rewarded
Show a rewarded ad when the reward button is clicked, and grant 100 coins on completion.
App Key: YOUR_APP_KEY
Unit ID: YOUR_UNIT_ID
```
### Banner Ad
```
Refer to https://docs.adrop.io/sdk/flutter/banner
Add a banner ad widget at the bottom of the screen.
App Key: YOUR_APP_KEY
Unit ID: YOUR_UNIT_ID
```
### Native Ad
```
Refer to https://docs.adrop.io/sdk/flutter/native
Add native ads between ListView feed items.
App Key: YOUR_APP_KEY
Unit ID: YOUR_UNIT_ID
```
### Interstitial Ad
```
Refer to https://docs.adrop.io/sdk/flutter/interstitial
Show an interstitial ad during screen transitions.
App Key: YOUR_APP_KEY
Unit ID: YOUR_UNIT_ID
```
### Rewarded Ad
```
Refer to https://docs.adrop.io/sdk/flutter/rewarded
Show a rewarded ad when the reward button is clicked, and grant 100 coins on completion.
App Key: YOUR_APP_KEY
Unit ID: YOUR_UNIT_ID
```
### Banner Ad
```
Refer to https://docs.adrop.io/sdk/react-native/banner
Add a banner ad component at the bottom of the screen.
App Key: YOUR_APP_KEY
Unit ID: YOUR_UNIT_ID
```
### Native Ad
```
Refer to https://docs.adrop.io/sdk/react-native/native
Add native ads between FlatList feed items.
App Key: YOUR_APP_KEY
Unit ID: YOUR_UNIT_ID
```
### Interstitial Ad
```
Refer to https://docs.adrop.io/sdk/react-native/interstitial
Show an interstitial ad during screen transitions.
App Key: YOUR_APP_KEY
Unit ID: YOUR_UNIT_ID
```
### Rewarded Ad
```
Refer to https://docs.adrop.io/sdk/react-native/rewarded
Show a rewarded ad when the reward button is clicked, and grant 100 coins on completion.
App Key: YOUR_APP_KEY
Unit ID: YOUR_UNIT_ID
```
### Banner Ad (NPM)
```
Refer to https://docs.adrop.io/sdk/web/react
Add a banner ad component at the bottom of the page.
App Key: YOUR_APP_KEY
Unit ID: YOUR_UNIT_ID
```
### Banner Ad (CDN)
```
Refer to https://docs.adrop.io/sdk/web/banner
Add a banner ad at the bottom of the page.
App Key: YOUR_APP_KEY
Unit ID: YOUR_UNIT_ID
```
### Native Ad
```
Refer to https://docs.adrop.io/sdk/web/native
Add native ads between content feed items.
App Key: YOUR_APP_KEY
Unit ID: YOUR_UNIT_ID
```
***
## Tips
**Be specific about placement**
Instead of "at the bottom of MainActivity", try "inside the ConstraintLayout in activity\_main.xml, aligned to the bottom" for more accurate implementation.
**Where to find App Key and Unit ID**
* **App Key**: [Console](https://console.adrop.io) → Settings → App
* **Unit ID**: [Console](https://console.adrop.io) → Ad Units
**Try with test keys first**
You can find test App Keys and Unit IDs in the Test Environment section of the [Prerequisites](/sdk/overview) page.
# Cursor
Source: https://docs.adrop.io/sdk/ai/cursor
Easily integrate the ad SDK with AI code editor
## Introduction
[Cursor](https://cursor.com) is a code editor with built-in AI.
It can automatically implement SDK integration by referencing the Adrop Help Center URL.
Copy the sample prompts below and just replace **App Key** and **Unit ID** to get started.
***
## Getting Started
### 1. Install Cursor
Download and install from [cursor.com](https://cursor.com).
### 2. Open your project
Open your project folder with Cursor.
### 3. Use Chat or Composer
* **Chat** (`Cmd/Ctrl + L`): Ask questions or request code modifications
* **Composer** (`Cmd/Ctrl + I`): Changes across multiple files
### 4. Enter a prompt
Copy and use the sample prompts below.
***
## Sample Prompts
### Banner Ad
```
@Docs https://docs.adrop.io/sdk/android/banner
Add a banner ad at the bottom of MainActivity.
App Key: YOUR_APP_KEY
Unit ID: YOUR_UNIT_ID
```
### Native Ad
```
@Docs https://docs.adrop.io/sdk/android/native
Add native ads between RecyclerView feed items.
App Key: YOUR_APP_KEY
Unit ID: YOUR_UNIT_ID
```
### Interstitial Ad
```
@Docs https://docs.adrop.io/sdk/android/interstitial
Show an interstitial ad during screen transitions.
App Key: YOUR_APP_KEY
Unit ID: YOUR_UNIT_ID
```
### Rewarded Ad
```
@Docs https://docs.adrop.io/sdk/android/rewarded
Show a rewarded ad when the reward button is clicked, and grant 100 coins on completion.
App Key: YOUR_APP_KEY
Unit ID: YOUR_UNIT_ID
```
### Banner Ad
```
@Docs https://docs.adrop.io/sdk/ios/banner
Add a banner ad at the bottom of ViewController.
App Key: YOUR_APP_KEY
Unit ID: YOUR_UNIT_ID
```
### Native Ad
```
@Docs https://docs.adrop.io/sdk/ios/native
Add native ads between TableView feed items.
App Key: YOUR_APP_KEY
Unit ID: YOUR_UNIT_ID
```
### Interstitial Ad
```
@Docs https://docs.adrop.io/sdk/ios/interstitial
Show an interstitial ad during screen transitions.
App Key: YOUR_APP_KEY
Unit ID: YOUR_UNIT_ID
```
### Rewarded Ad
```
@Docs https://docs.adrop.io/sdk/ios/rewarded
Show a rewarded ad when the reward button is clicked, and grant 100 coins on completion.
App Key: YOUR_APP_KEY
Unit ID: YOUR_UNIT_ID
```
### Banner Ad
```
@Docs https://docs.adrop.io/sdk/flutter/banner
Add a banner ad widget at the bottom of the screen.
App Key: YOUR_APP_KEY
Unit ID: YOUR_UNIT_ID
```
### Native Ad
```
@Docs https://docs.adrop.io/sdk/flutter/native
Add native ads between ListView feed items.
App Key: YOUR_APP_KEY
Unit ID: YOUR_UNIT_ID
```
### Interstitial Ad
```
@Docs https://docs.adrop.io/sdk/flutter/interstitial
Show an interstitial ad during screen transitions.
App Key: YOUR_APP_KEY
Unit ID: YOUR_UNIT_ID
```
### Rewarded Ad
```
@Docs https://docs.adrop.io/sdk/flutter/rewarded
Show a rewarded ad when the reward button is clicked, and grant 100 coins on completion.
App Key: YOUR_APP_KEY
Unit ID: YOUR_UNIT_ID
```
### Banner Ad
```
@Docs https://docs.adrop.io/sdk/react-native/banner
Add a banner ad component at the bottom of the screen.
App Key: YOUR_APP_KEY
Unit ID: YOUR_UNIT_ID
```
### Native Ad
```
@Docs https://docs.adrop.io/sdk/react-native/native
Add native ads between FlatList feed items.
App Key: YOUR_APP_KEY
Unit ID: YOUR_UNIT_ID
```
### Interstitial Ad
```
@Docs https://docs.adrop.io/sdk/react-native/interstitial
Show an interstitial ad during screen transitions.
App Key: YOUR_APP_KEY
Unit ID: YOUR_UNIT_ID
```
### Rewarded Ad
```
@Docs https://docs.adrop.io/sdk/react-native/rewarded
Show a rewarded ad when the reward button is clicked, and grant 100 coins on completion.
App Key: YOUR_APP_KEY
Unit ID: YOUR_UNIT_ID
```
### Banner Ad (NPM)
```
@Docs https://docs.adrop.io/sdk/web/react
Add a banner ad component at the bottom of the page.
App Key: YOUR_APP_KEY
Unit ID: YOUR_UNIT_ID
```
### Banner Ad (CDN)
```
@Docs https://docs.adrop.io/sdk/web/banner
Add a banner ad at the bottom of the page.
App Key: YOUR_APP_KEY
Unit ID: YOUR_UNIT_ID
```
### Native Ad
```
@Docs https://docs.adrop.io/sdk/web/native
Add native ads between content feed items.
App Key: YOUR_APP_KEY
Unit ID: YOUR_UNIT_ID
```
***
## Tips
**Referencing docs with @Docs**
In Cursor, use the `@Docs` command to directly reference external documentation.
Add `@Docs URL` at the beginning of your prompt to use that document as context.
**Be specific about placement**
Instead of "at the bottom of MainActivity", try "inside the ConstraintLayout in activity\_main.xml, aligned to the bottom" for more accurate implementation.
**Where to find App Key and Unit ID**
* **App Key**: [Console](https://console.adrop.io) → Settings → App
* **Unit ID**: [Console](https://console.adrop.io) → Ad Units
**Try with test keys first**
You can find test App Keys and Unit IDs in the Test Environment section of the [Prerequisites](/sdk/overview) page.
# Banner Ads
Source: https://docs.adrop.io/sdk/android/banner
Learn how to implement banner ads in your Android app.
## Overview
Banner ads are rectangular ads displayed in a portion of the screen. They can be implemented using both XML layout and code.
### Key Features
* Can be fixed at the top, bottom, or middle of the screen
* Supports image and video ads
* Supports both XML layout and programmatic implementation
* Handle ad events through listeners
Use test unit ID during development: `PUBLIC_TEST_UNIT_ID_320_100`
***
## XML Layout Implementation
Define `AdropBanner` in XML layout and load it in Activity or Fragment.
### 1. Define XML Layout
Place `AdropBanner` in a `FrameLayout` or other container.
```xml activity_main.xml theme={null}
```
### 2. Load in Activity
```kotlin Kotlin theme={null}
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import io.adrop.ads.banner.AdropBanner
import io.adrop.ads.banner.AdropBannerListener
import io.adrop.ads.model.AdropErrorCode
class MainActivity : AppCompatActivity() {
private var banner: AdropBanner? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 1. Find banner view defined in XML
banner = findViewById(R.id.adrop_banner)
// 2. Set listener
banner?.listener = object : AdropBannerListener {
override fun onAdReceived(banner: AdropBanner) {
println("Banner ad received successfully")
}
override fun onAdFailedToReceive(banner: AdropBanner, errorCode: AdropErrorCode) {
println("Banner ad failed to receive: $errorCode")
}
override fun onAdImpression(banner: AdropBanner) {
println("Banner ad impression")
}
override fun onAdClicked(banner: AdropBanner) {
println("Banner ad clicked")
}
}
// 3. Load ad
banner?.load()
}
override fun onDestroy() {
super.onDestroy()
// 4. Release memory
banner?.destroy()
banner = null
}
}
```
```java Java theme={null}
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import io.adrop.ads.banner.AdropBanner;
import io.adrop.ads.banner.AdropBannerListener;
import io.adrop.ads.model.AdropErrorCode;
import org.jetbrains.annotations.NotNull;
public class MainActivity extends AppCompatActivity {
private AdropBanner banner;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 1. Find banner view defined in XML
banner = findViewById(R.id.adrop_banner);
// 2. Set listener
banner.setListener(new AdropBannerListener() {
@Override
public void onAdReceived(@NotNull AdropBanner banner) {
System.out.println("Banner ad received successfully");
}
@Override
public void onAdFailedToReceive(@NotNull AdropBanner banner, @NotNull AdropErrorCode errorCode) {
System.out.println("Banner ad failed to receive: " + errorCode);
}
@Override
public void onAdImpression(@NotNull AdropBanner banner) {
System.out.println("Banner ad impression");
}
@Override
public void onAdClicked(@NotNull AdropBanner banner) {
System.out.println("Banner ad clicked");
}
});
// 3. Load ad
banner.load();
}
@Override
protected void onDestroy() {
super.onDestroy();
// 4. Release memory
if (banner != null) {
banner.destroy();
banner = null;
}
}
}
```
### XML Attributes
| Attribute | Description | Required |
| - | - | :-: |
| `app:adrop_unit_id` | Ad unit ID | O |
| `app:adrop_context_id` | Context ID for contextual targeting | |
| `android:layout_width` | Banner width (dp) | O |
| `android:layout_height` | Banner height (dp) | O |
***
## Programmatic Implementation
Create `AdropBanner` instance directly in code and add it to the view.
```kotlin Kotlin theme={null}
import android.os.Bundle
import android.widget.FrameLayout
import androidx.appcompat.app.AppCompatActivity
import io.adrop.ads.banner.AdropBanner
import io.adrop.ads.banner.AdropBannerListener
import io.adrop.ads.model.AdropErrorCode
class MainActivity : AppCompatActivity() {
private var banner: AdropBanner? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 1. Create banner instance
banner = AdropBanner(this, "YOUR_UNIT_ID").apply {
// 2. Set listener
listener = object : AdropBannerListener {
override fun onAdReceived(banner: AdropBanner) {
println("Banner ad received successfully")
}
override fun onAdFailedToReceive(banner: AdropBanner, errorCode: AdropErrorCode) {
println("Banner ad failed to receive: $errorCode")
}
override fun onAdImpression(banner: AdropBanner) {
println("Banner ad impression")
}
override fun onAdClicked(banner: AdropBanner) {
println("Banner ad clicked")
}
}
}
// 3. Add to view hierarchy
val container = findViewById(R.id.banner_container)
val params = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT
)
container.addView(banner, params)
// 4. Load ad
banner?.load()
}
override fun onDestroy() {
super.onDestroy()
// 5. Release memory
banner?.destroy()
banner = null
}
}
```
```java Java theme={null}
import android.os.Bundle;
import android.widget.FrameLayout;
import androidx.appcompat.app.AppCompatActivity;
import io.adrop.ads.banner.AdropBanner;
import io.adrop.ads.banner.AdropBannerListener;
import io.adrop.ads.model.AdropErrorCode;
import org.jetbrains.annotations.NotNull;
public class MainActivity extends AppCompatActivity {
private AdropBanner banner;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 1. Create banner instance (unitId is required in constructor)
banner = new AdropBanner(this, "YOUR_UNIT_ID");
// 2. Set listener
banner.setListener(new AdropBannerListener() {
@Override
public void onAdReceived(@NotNull AdropBanner banner) {
System.out.println("Banner ad received successfully");
}
@Override
public void onAdFailedToReceive(@NotNull AdropBanner banner, @NotNull AdropErrorCode errorCode) {
System.out.println("Banner ad failed to receive: " + errorCode);
}
@Override
public void onAdImpression(@NotNull AdropBanner banner) {
System.out.println("Banner ad impression");
}
@Override
public void onAdClicked(@NotNull AdropBanner banner) {
System.out.println("Banner ad clicked");
}
});
// 3. Add to view hierarchy
FrameLayout container = findViewById(R.id.banner_container);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT
);
container.addView(banner, params);
// 4. Load ad
banner.load();
}
@Override
protected void onDestroy() {
super.onDestroy();
// 5. Release memory
if (banner != null) {
banner.destroy();
banner = null;
}
}
}
```
***
## AdropBanner Initialization
### Constructor
Activity or Application Context
```kotlin Kotlin theme={null}
val banner = AdropBanner(context, "YOUR_UNIT_ID")
```
```java Java theme={null}
AdropBanner banner = new AdropBanner(context, "YOUR_UNIT_ID");
```
### Set Unit ID
Unit ID created in Ad Control Console
```kotlin Kotlin theme={null}
banner.setUnitId("YOUR_UNIT_ID")
```
```java Java theme={null}
banner.setUnitId("YOUR_UNIT_ID");
```
### Load Ad
Call `load()` method after adding the banner to the screen to request ads.
```kotlin Kotlin theme={null}
banner.load()
```
```java Java theme={null}
banner.load();
```
Call `load()` when the banner is visible on screen. Loading when not visible may result in inaccurate impression measurement.
***
## Listener Implementation
The `AdropBannerListener` interface handles ad lifecycle events.
### onAdReceived (Required)
Called when ad is successfully received.
```kotlin Kotlin theme={null}
override fun onAdReceived(banner: AdropBanner) {
println("Banner ad received successfully")
// Hide loading indicator, etc.
}
```
```java Java theme={null}
@Override
public void onAdReceived(@NotNull AdropBanner banner) {
System.out.println("Banner ad received successfully");
// Hide loading indicator, etc.
}
```
### onAdFailedToReceive (Required)
Called when ad fails to receive.
```kotlin Kotlin theme={null}
override fun onAdFailedToReceive(banner: AdropBanner, errorCode: AdropErrorCode) {
println("Banner ad failed to receive: $errorCode")
// Handle error and show fallback content
}
```
```java Java theme={null}
@Override
public void onAdFailedToReceive(@NotNull AdropBanner banner, @NotNull AdropErrorCode errorCode) {
System.out.println("Banner ad failed to receive: " + errorCode);
// Handle error and show fallback content
}
```
Code indicating error type. See [Reference](/sdk/android/reference#error-codes) for details.
### onAdImpression (Optional)
Called when ad is displayed on screen.
```kotlin Kotlin theme={null}
override fun onAdImpression(banner: AdropBanner) {
println("Banner ad impression")
// Log impression analytics, etc.
}
```
```java Java theme={null}
@Override
public void onAdImpression(@NotNull AdropBanner banner) {
System.out.println("Banner ad impression");
// Log impression analytics, etc.
}
```
### onAdClicked (Required)
Called when user clicks the ad.
```kotlin Kotlin theme={null}
override fun onAdClicked(banner: AdropBanner) {
println("Banner ad clicked")
// Log click analytics, etc.
}
```
```java Java theme={null}
@Override
public void onAdClicked(@NotNull AdropBanner banner) {
System.out.println("Banner ad clicked");
// Log click analytics, etc.
}
```
### onAdVideoStart (Optional)
Called when video playback starts in a video banner ad.
```kotlin Kotlin theme={null}
override fun onAdVideoStart(banner: AdropBanner) {
println("Banner video started")
}
```
```java Java theme={null}
@Override
public void onAdVideoStart(@NotNull AdropBanner banner) {
System.out.println("Banner video started");
}
```
### onAdVideoEnd (Optional)
Called when video playback ends in a video banner ad.
```kotlin Kotlin theme={null}
override fun onAdVideoEnd(banner: AdropBanner) {
println("Banner video ended")
}
```
```java Java theme={null}
@Override
public void onAdVideoEnd(@NotNull AdropBanner banner) {
System.out.println("Banner video ended");
}
```
***
## Context ID Setting
You can set Context ID for [contextual targeting](/sdk/android/targeting#contextual-targeting).
```kotlin Kotlin theme={null}
// contextId is read-only — set via constructor
val banner = AdropBanner(this, "YOUR_UNIT_ID", "article_sports")
```
```java Java theme={null}
// contextId is read-only — set via constructor
AdropBanner banner = new AdropBanner(this, "YOUR_UNIT_ID", "article_sports");
```
Context ID must be set before loading the ad.
***
## Lifecycle Management
### destroy()
Always call `destroy()` to release resources when the banner is no longer used.
```kotlin Kotlin theme={null}
override fun onDestroy() {
super.onDestroy()
banner?.destroy()
banner = null
}
```
```java Java theme={null}
@Override
protected void onDestroy() {
super.onDestroy();
if (banner != null) {
banner.destroy();
banner = null;
}
}
```
Memory leaks may occur if `destroy()` is not called.
***
## Ad Sizes
Banner ads should have view size matching the unit settings.
### Common Banner Sizes
| Size | Usage |
| - | - |
| 320 x 50 | Small banner |
| 320 x 100 | Medium banner |
| 16:9 ratio | Video banner |
### Specify Size in XML
```xml theme={null}
```
### Specify Size in Code
```kotlin Kotlin theme={null}
val params = FrameLayout.LayoutParams(
dpToPx(320), // width
dpToPx(100) // height
)
container.addView(banner, params)
// Utility function to convert dp to px
fun dpToPx(dp: Int): Int {
val density = resources.displayMetrics.density
return (dp * density).toInt()
}
```
```java Java theme={null}
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
dpToPx(320), // width
dpToPx(100) // height
);
container.addView(banner, params);
// Utility function to convert dp to px
private int dpToPx(int dp) {
float density = getResources().getDisplayMetrics().density;
return (int) (dp * density);
}
```
***
## Test Unit IDs
Use the following test unit IDs during development and testing.
| Ad Type | Test Unit ID | Size |
| - | - | - |
| Banner (320x50) | `PUBLIC_TEST_UNIT_ID_320_50` | 320 x 50 |
| Banner (320x100) | `PUBLIC_TEST_UNIT_ID_320_100` | 320 x 100 |
| Carousel Banner | `PUBLIC_TEST_UNIT_ID_CAROUSEL` | Variable |
| Banner Video (16:9) | `PUBLIC_TEST_UNIT_ID_BANNER_VIDEO_16_9` | 16:9 ratio |
| Banner Video (9:16) | `PUBLIC_TEST_UNIT_ID_BANNER_VIDEO_9_16` | 9:16 ratio |
### Usage Example
```kotlin Kotlin theme={null}
val banner = AdropBanner(this, "PUBLIC_TEST_UNIT_ID_320_100")
```
```java Java theme={null}
AdropBanner banner = new AdropBanner(this, "PUBLIC_TEST_UNIT_ID_320_100");
```
```xml XML theme={null}
```
***
## Video Playback Control
For video banner ads, you can manually control video playback using `play()` and `pause()`.
### play()
Resumes video playback. Call this when the banner becomes visible again — for example, after a popup is dismissed or the app returns from the background.
```kotlin Kotlin theme={null}
banner?.play()
```
```java Java theme={null}
banner.play();
```
### pause()
Pauses video playback. Call this when the banner is hidden by a popup or the app enters the background.
```kotlin Kotlin theme={null}
banner?.pause()
```
```java Java theme={null}
banner.pause();
```
### Example: Background/Foreground Handling
```kotlin Kotlin theme={null}
override fun onResume() {
super.onResume()
banner?.play()
}
override fun onPause() {
super.onPause()
banner?.pause()
}
```
```java Java theme={null}
@Override
protected void onResume() {
super.onResume();
if (banner != null) {
banner.play();
}
}
@Override
protected void onPause() {
super.onPause();
if (banner != null) {
banner.pause();
}
}
```
`play()` and `pause()` only affect video banner ads. They have no effect on image banners.
***
## Custom Click Handling
By default, the SDK automatically opens the destination URL when a user clicks the banner ad. If you want to handle click events yourself (e.g., to show a confirmation dialog or track analytics before opening), you can use `useCustomClick`.
When `true`, the SDK will not automatically open the destination URL on click. Only the `onAdClicked` event is fired. You must call `open()` manually to open the destination.
```kotlin Kotlin theme={null}
// 1. Enable custom click handling
banner?.useCustomClick = true
// 2. Handle click in listener
banner?.listener = object : AdropBannerListener {
override fun onAdClicked(banner: AdropBanner) {
// Perform custom logic (e.g., show dialog, log analytics)
println("Ad clicked — opening destination")
banner.open()
}
// ... other listener methods
}
```
```java Java theme={null}
// 1. Enable custom click handling
banner.setUseCustomClick(true);
// 2. Handle click in listener
banner.setListener(new AdropBannerListener() {
@Override
public void onAdClicked(@NotNull AdropBanner banner) {
// Perform custom logic (e.g., show dialog, log analytics)
System.out.println("Ad clicked — opening destination");
banner.open();
}
// ... other listener methods
});
```
### open()
Opens the ad's destination URL. You can also pass a custom URL to open instead.
```kotlin Kotlin theme={null}
// Open the default destination URL
banner?.open()
// Open a custom URL
banner?.open("https://example.com")
```
```java Java theme={null}
// Open the default destination URL
banner.open();
// Open a custom URL
banner.open("https://example.com");
```
`open()` only works when `useCustomClick` is set to `true`. If `useCustomClick` is `false` (default), the SDK handles URL opening automatically.
***
## Best Practices
### 1. Memory Management
Always release the banner when Activity or Fragment is destroyed.
```kotlin Kotlin theme={null}
override fun onDestroy() {
super.onDestroy()
banner?.destroy()
banner = null
}
```
```java Java theme={null}
@Override
protected void onDestroy() {
super.onDestroy();
if (banner != null) {
banner.destroy();
banner = null;
}
}
```
### 2. Screen Visibility
Load ads when the banner is visible on screen.
```kotlin Kotlin theme={null}
override fun onResume() {
super.onResume()
banner?.load()
}
```
```java Java theme={null}
@Override
protected void onResume() {
super.onResume();
if (banner != null) {
banner.load();
}
}
```
### 3. Error Handling
Implement proper error handling when ad load fails.
```kotlin Kotlin theme={null}
override fun onAdFailedToReceive(banner: AdropBanner, errorCode: AdropErrorCode) {
when (errorCode) {
AdropErrorCode.ERROR_CODE_NETWORK -> {
println("Network error: Check connection")
}
AdropErrorCode.ERROR_CODE_AD_NO_FILL -> {
println("No ads available")
}
else -> {
println("Ad load failed: $errorCode")
}
}
}
```
```java Java theme={null}
@Override
public void onAdFailedToReceive(@NotNull AdropBanner banner, @NotNull AdropErrorCode errorCode) {
if (errorCode == AdropErrorCode.ERROR_CODE_NETWORK) {
System.out.println("Network error: Check connection");
} else if (errorCode == AdropErrorCode.ERROR_CODE_AD_NO_FILL) {
System.out.println("No ads available");
} else {
System.out.println("Ad load failed: " + errorCode);
}
}
```
### 4. Using in RecyclerView
Properly manage banners in ViewHolder when using in RecyclerView.
```kotlin Kotlin theme={null}
class BannerViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val banner: AdropBanner = view.findViewById(R.id.adrop_banner)
fun bind() {
banner.listener = object : AdropBannerListener {
// Implement listener
}
banner.load()
}
fun recycle() {
banner.destroy()
}
}
// In adapter
override fun onViewRecycled(holder: BannerViewHolder) {
holder.recycle()
}
```
```java Java theme={null}
class BannerViewHolder extends RecyclerView.ViewHolder {
private AdropBanner banner;
public BannerViewHolder(View view) {
super(view);
banner = view.findViewById(R.id.adrop_banner);
}
public void bind() {
banner.setListener(new AdropBannerListener() {
// Implement listener
});
banner.load();
}
public void recycle() {
banner.destroy();
}
}
// In adapter
@Override
public void onViewRecycled(BannerViewHolder holder) {
holder.recycle();
}
```
***
## Complete Example
### Kotlin Example
```kotlin theme={null}
import android.os.Bundle
import android.view.View
import android.widget.FrameLayout
import android.widget.ProgressBar
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import io.adrop.ads.banner.AdropBanner
import io.adrop.ads.banner.AdropBannerListener
import io.adrop.ads.model.AdropErrorCode
class BannerActivity : AppCompatActivity() {
private var banner: AdropBanner? = null
private lateinit var progressBar: ProgressBar
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_banner)
progressBar = findViewById(R.id.progress_bar)
setupBanner()
}
private fun setupBanner() {
// 1. Create banner instance
// contextId is set via constructor (read-only property)
banner = AdropBanner(this, "YOUR_UNIT_ID", "article_sports").apply {
// 2. Set listener
listener = object : AdropBannerListener {
override fun onAdReceived(banner: AdropBanner) {
println("Banner ad received successfully")
progressBar.visibility = View.GONE
}
override fun onAdFailedToReceive(banner: AdropBanner, errorCode: AdropErrorCode) {
println("Banner ad failed to receive: $errorCode")
progressBar.visibility = View.GONE
// Handle based on error
when (errorCode) {
AdropErrorCode.ERROR_CODE_NETWORK -> {
showToast("Please check network connection")
}
AdropErrorCode.ERROR_CODE_AD_NO_FILL -> {
println("No ads available")
}
else -> {
showToast("Unable to load ad")
}
}
}
override fun onAdImpression(banner: AdropBanner) {
println("Banner ad impression")
}
override fun onAdClicked(banner: AdropBanner) {
println("Banner ad clicked")
}
}
}
// 3. Add to view hierarchy
val container = findViewById(R.id.banner_container)
val params = FrameLayout.LayoutParams(
dpToPx(320),
dpToPx(100)
)
container.addView(banner, params)
// 4. Load ad
progressBar.visibility = View.VISIBLE
banner?.load()
}
private fun dpToPx(dp: Int): Int {
val density = resources.displayMetrics.density
return (dp * density).toInt()
}
private fun showToast(message: String) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
override fun onDestroy() {
super.onDestroy()
// 5. Release memory
banner?.destroy()
banner = null
}
}
```
### Java Example
```java theme={null}
import android.os.Bundle;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ProgressBar;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import io.adrop.ads.banner.AdropBanner;
import io.adrop.ads.banner.AdropBannerListener;
import io.adrop.ads.model.AdropErrorCode;
import org.jetbrains.annotations.NotNull;
public class BannerActivity extends AppCompatActivity {
private AdropBanner banner;
private ProgressBar progressBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_banner);
progressBar = findViewById(R.id.progress_bar);
setupBanner();
}
private void setupBanner() {
// 1. Create banner instance (with context ID)
banner = new AdropBanner(this, "YOUR_UNIT_ID", "article_sports");
// 2. Set listener
banner.setListener(new AdropBannerListener() {
@Override
public void onAdReceived(@NotNull AdropBanner banner) {
System.out.println("Banner ad received successfully");
progressBar.setVisibility(View.GONE);
}
@Override
public void onAdFailedToReceive(@NotNull AdropBanner banner, @NotNull AdropErrorCode errorCode) {
System.out.println("Banner ad failed to receive: " + errorCode);
progressBar.setVisibility(View.GONE);
// Handle based on error
if (errorCode == AdropErrorCode.ERROR_CODE_NETWORK) {
showToast("Please check network connection");
} else if (errorCode == AdropErrorCode.ERROR_CODE_AD_NO_FILL) {
System.out.println("No ads available");
} else {
showToast("Unable to load ad");
}
}
@Override
public void onAdImpression(@NotNull AdropBanner banner) {
System.out.println("Banner ad impression");
}
@Override
public void onAdClicked(@NotNull AdropBanner banner) {
System.out.println("Banner ad clicked");
}
});
// 3. Add to view hierarchy
FrameLayout container = findViewById(R.id.banner_container);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
dpToPx(320),
dpToPx(100)
);
container.addView(banner, params);
// 4. Load ad
progressBar.setVisibility(View.VISIBLE);
banner.load();
}
private int dpToPx(int dp) {
float density = getResources().getDisplayMetrics().density;
return (int) (dp * density);
}
private void showToast(String message) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
@Override
protected void onDestroy() {
super.onDestroy();
// 5. Release memory
if (banner != null) {
banner.destroy();
banner = null;
}
}
}
```
### XML Layout
```xml activity_banner.xml theme={null}
```
***
## Batch Loading (loads)
Use `AdropBanner.loads()` to request multiple banner ads in a single batched call. This is useful for building carousels, paginated feeds, or prefetching a pool of ads.
### Signature
```kotlin Kotlin theme={null}
AdropBanner.loads(
context: Context,
unitId: String,
contextId: String? = null,
listener: AdropBannerListener,
)
```
```java Java theme={null}
AdropBanner.loads(context, "YOUR_UNIT_ID", null, listener);
```
### Usage
```kotlin Kotlin theme={null}
AdropBanner.loads(
context = this,
unitId = "YOUR_UNIT_ID",
contextId = null,
listener = object : AdropBannerListener {
// Batch success — deliver up to 5 ready-to-use banners
override fun onAdsReceived(banners: List) {
banners.forEach { banner ->
// Attach to your view hierarchy (ViewPager2, RecyclerView, etc.)
container.addView(banner)
}
}
// Batch failure
override fun onAdsFailedToReceive(errorCode: AdropErrorCode) {
println("Batch load failed: $errorCode")
}
// Single-load callbacks are NOT invoked on this path.
override fun onAdReceived(banner: AdropBanner) { /* batch path */ }
override fun onAdFailedToReceive(banner: AdropBanner, errorCode: AdropErrorCode) {}
// Click / impression / video callbacks still fire per instance.
override fun onAdClicked(banner: AdropBanner) {
println("Banner clicked: ${banner.creativeId}")
}
override fun onAdImpression(banner: AdropBanner) {
println("Banner impression: ${banner.creativeId}")
}
},
)
```
```java Java theme={null}
AdropBanner.loads(this, "YOUR_UNIT_ID", null, new AdropBannerListener() {
@Override
public void onAdsReceived(@NotNull List banners) {
for (AdropBanner banner : banners) {
container.addView(banner);
}
}
@Override
public void onAdsFailedToReceive(@NotNull AdropErrorCode errorCode) {
System.out.println("Batch load failed: " + errorCode);
}
// Required single-load callbacks — unused on the batch path.
@Override
public void onAdReceived(@NotNull AdropBanner banner) { }
@Override
public void onAdFailedToReceive(@NotNull AdropBanner banner, @NotNull AdropErrorCode errorCode) { }
@Override
public void onAdClicked(@NotNull AdropBanner banner) {
System.out.println("Banner clicked: " + banner.getCreativeId());
}
@Override
public void onAdImpression(@NotNull AdropBanner banner) {
System.out.println("Banner impression: " + banner.getCreativeId());
}
});
```
### Listener Callbacks
| Callback | When it fires |
| - | - |
| `onAdsReceived(banners)` | Batch delivered. Each `AdropBanner` already has the listener attached and its ad data applied. |
| `onAdsFailedToReceive(errorCode)` | Request rejected, network failure, or no fillable ads were returned (`ERROR_CODE_AD_NO_FILL`). |
| `onAdClicked` / `onAdImpression` / `onAdVideoStart` / `onAdVideoEnd` | Fire on each returned banner just like a single `load()`. |
The singular `onAdReceived` / `onAdFailedToReceive` callbacks are **not** invoked on the `loads()` path. Use `onAdsReceived` / `onAdsFailedToReceive` as the sole batch signals.
### Constraints
* **Maximum 5 ads per call.** If the server returns more, only the first 5 are delivered.
* **Backfill is not applied.** If no direct ads fill, `onAdsFailedToReceive` is called with `ERROR_CODE_AD_NO_FILL` regardless of backfill configuration.
* **Do not call `destroy()` inside `onAdsReceived`.** Attach each banner to a view hierarchy first, then `destroy()` on the parent `Activity.onDestroy()` (or equivalent).
* `onAdsReceived` fires as soon as ad data is applied, matching the single `load()` contract — the creative paints shortly after attachment.
***
## Backfill Ads
When backfill ads are enabled, backfill ads are automatically loaded when no direct ads are available. You can check if an ad is a backfill ad using the `isBackfilled` property.
```kotlin Kotlin theme={null}
banner.listener = object : AdropBannerListener {
override fun onAdReceived(banner: AdropBanner) {
if (banner.isBackfilled) {
println("Backfill ad loaded")
} else {
println("Direct ad loaded")
}
}
override fun onAdFailedToReceive(banner: AdropBanner, errorCode: AdropErrorCode) {
when (errorCode) {
AdropErrorCode.ERROR_CODE_AD_NO_FILL -> {
println("No direct ad, requesting backfill...")
}
AdropErrorCode.ERROR_CODE_AD_BACKFILL_NO_FILL -> {
println("Backfill ad also not available")
}
else -> {
println("Ad load failed: $errorCode")
}
}
}
}
```
```java Java theme={null}
banner.setListener(new AdropBannerListener() {
@Override
public void onAdReceived(@NotNull AdropBanner banner) {
if (banner.isBackfilled()) {
System.out.println("Backfill ad loaded");
} else {
System.out.println("Direct ad loaded");
}
}
@Override
public void onAdFailedToReceive(@NotNull AdropBanner banner, @NotNull AdropErrorCode errorCode) {
if (errorCode == AdropErrorCode.ERROR_CODE_AD_NO_FILL) {
System.out.println("No direct ad, requesting backfill...");
} else if (errorCode == AdropErrorCode.ERROR_CODE_AD_BACKFILL_NO_FILL) {
System.out.println("Backfill ad also not available");
} else {
System.out.println("Ad load failed: " + errorCode);
}
}
});
```
To use backfill ads, you must add the `io.adrop:adrop-ads-backfill` dependency.
***
## Next Steps
Implement customizable native ads that match your UI
Implement full-screen interstitial ads
Set up user attributes and contextual targeting
View classes, listeners, and error codes
# Examples
Source: https://docs.adrop.io/sdk/android/examples
Android SDK example code and sample projects.
## Overview
Check out the example code and sample projects for the Adrop Android SDK.
***
## Sample Project
You can find the complete sample project on GitHub.
Sample Android app written in Kotlin
***
## Quick Start Examples
### Banner Ad (Kotlin)
```kotlin theme={null}
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import io.adrop.ads.banner.AdropBanner
class BannerActivity : AppCompatActivity() {
private lateinit var adropBanner: AdropBanner
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_banner)
// Initialize banner ad
adropBanner = findViewById(R.id.adrop_banner)
adropBanner.setUnitId("YOUR_UNIT_ID")
// Set ad listener (optional)
adropBanner.listener = object : AdropBannerListener {
override fun onAdReceived(banner: AdropBanner) {
// Ad received successfully
}
override fun onAdClicked(banner: AdropBanner) {
// Ad clicked
}
override fun onAdFailedToReceive(banner: AdropBanner, errorCode: AdropErrorCode) {
// Failed to receive ad
}
}
// Load ad
adropBanner.load()
}
}
```
**Layout file (activity\_banner.xml)**
```xml theme={null}
```
***
### Native Ad (Kotlin)
```kotlin theme={null}
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import io.adrop.ads.nativeAd.AdropNativeAd
import io.adrop.ads.nativeAd.AdropNativeAdView
class NativeAdActivity : AppCompatActivity() {
private lateinit var nativeAd: AdropNativeAd
private lateinit var nativeAdView: AdropNativeAdView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_native)
// Initialize native ad
nativeAd = AdropNativeAd(this, "YOUR_UNIT_ID")
// Set ad listener
nativeAd.listener = object : AdropNativeAdListener {
override fun onAdReceived(ad: AdropNativeAd) {
// Ad received successfully - bind UI
displayNativeAd(ad)
}
override fun onAdClicked(ad: AdropNativeAd) {
// Ad clicked
}
override fun onAdFailedToReceive(ad: AdropNativeAd, errorCode: AdropErrorCode) {
// Failed to receive ad
}
}
// Load ad
nativeAd.load()
}
private fun displayNativeAd(ad: AdropNativeAd) {
// Inflate native ad view
val inflater = LayoutInflater.from(this)
nativeAdView = inflater.inflate(
R.layout.native_ad_layout,
null
) as AdropNativeAdView
// Bind ad elements
val iconView = nativeAdView.findViewById(R.id.ad_icon)
val titleView = nativeAdView.findViewById(R.id.ad_title)
val bodyView = nativeAdView.findViewById(R.id.ad_body)
val ctaButton = nativeAdView.findViewById(R.id.ad_cta)
// Set ad data
// icon is a URL string — use an image loading library
Glide.with(this).load(ad.icon).into(iconView)
titleView.text = ad.headline
bodyView.text = ad.body
ctaButton.text = ad.callToAction
// Register ad with native ad view
nativeAdView.setNativeAd(ad)
// Add to container
val adContainer = findViewById(R.id.native_ad_container)
adContainer.removeAllViews()
adContainer.addView(nativeAdView)
}
override fun onDestroy() {
nativeAd.destroy()
super.onDestroy()
}
}
```
**Native ad layout (native\_ad\_layout.xml)**
```xml theme={null}
```
***
### Interstitial Ad (Kotlin)
```kotlin theme={null}
import android.os.Bundle
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity
import io.adrop.ads.interstitial.AdropInterstitialAd
class InterstitialActivity : AppCompatActivity() {
private var interstitialAd: AdropInterstitialAd? = null
private lateinit var showButton: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_interstitial)
showButton = findViewById(R.id.show_interstitial_button)
showButton.isEnabled = false
// Load interstitial ad
loadInterstitialAd()
showButton.setOnClickListener {
showInterstitialAd()
}
}
private fun loadInterstitialAd() {
interstitialAd = AdropInterstitialAd(this, "YOUR_UNIT_ID")
interstitialAd?.interstitialAdListener = object : AdropInterstitialAdListener {
override fun onAdReceived(ad: AdropInterstitialAd) {
// Ad loaded - ready to show
showButton.isEnabled = true
}
override fun onAdFailedToReceive(ad: AdropInterstitialAd, errorCode: AdropErrorCode) {
// Failed to load ad
showButton.isEnabled = false
}
override fun onAdFailedToShowFullScreen(ad: AdropInterstitialAd, errorCode: AdropErrorCode) {
// Failed to show ad
}
override fun onAdDidPresentFullScreen(ad: AdropInterstitialAd) {
// Interstitial ad displayed on screen
}
override fun onAdDidDismissFullScreen(ad: AdropInterstitialAd) {
// Interstitial ad closed - load new ad
loadInterstitialAd()
}
override fun onAdClicked(ad: AdropInterstitialAd) {
// Ad clicked
}
}
interstitialAd?.load()
}
private fun showInterstitialAd() {
interstitialAd?.let { ad ->
if (ad.isLoaded) {
ad.show(this@InterstitialActivity)
}
}
}
override fun onDestroy() {
interstitialAd?.destroy()
super.onDestroy()
}
}
```
***
### Rewarded Ad (Kotlin)
```kotlin theme={null}
import android.os.Bundle
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import io.adrop.ads.rewardedAd.AdropRewardedAd
class RewardedAdActivity : AppCompatActivity() {
private var rewardedAd: AdropRewardedAd? = null
private lateinit var showButton: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_rewarded)
showButton = findViewById(R.id.show_rewarded_button)
showButton.isEnabled = false
// Load rewarded ad
loadRewardedAd()
showButton.setOnClickListener {
showRewardedAd()
}
}
private fun loadRewardedAd() {
rewardedAd = AdropRewardedAd(this, "YOUR_UNIT_ID")
rewardedAd?.rewardedAdListener = object : AdropRewardedAdListener {
override fun onAdReceived(ad: AdropRewardedAd) {
// Ad loaded - ready to show
showButton.isEnabled = true
}
override fun onAdFailedToReceive(ad: AdropRewardedAd, errorCode: AdropErrorCode) {
// Failed to load ad
showButton.isEnabled = false
}
override fun onAdFailedToShowFullScreen(ad: AdropRewardedAd, errorCode: AdropErrorCode) {
// Failed to show ad
}
override fun onAdDidPresentFullScreen(ad: AdropRewardedAd) {
// Full screen ad displayed
}
override fun onAdDidDismissFullScreen(ad: AdropRewardedAd) {
// Ad closed - load new ad
loadRewardedAd()
}
override fun onAdClicked(ad: AdropRewardedAd) {
// Ad clicked
}
// Note: Reward handling is done via the show() method's second parameter (AdropUserDidEarnRewardHandler)
}
rewardedAd?.load()
}
private fun showRewardedAd() {
rewardedAd?.let { ad ->
if (ad.isLoaded) {
ad.show(this@RewardedAdActivity) { type, amount ->
Toast.makeText(
this@RewardedAdActivity,
"Reward earned: $amount of type $type",
Toast.LENGTH_SHORT
).show()
grantReward(type, amount)
}
}
}
}
private fun grantReward(type: Int, amount: Int) {
// Logic to grant reward to user
// e.g., game coins, lives, items, etc.
}
override fun onDestroy() {
rewardedAd?.destroy()
super.onDestroy()
}
}
```
***
## Java Examples
### Banner Ad (Java)
```java theme={null}
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import io.adrop.ads.banner.AdropBanner;
public class BannerActivity extends AppCompatActivity {
private AdropBanner adropBanner;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_banner);
// Initialize banner ad
adropBanner = findViewById(R.id.adrop_banner);
adropBanner.setUnitId("YOUR_UNIT_ID");
// Set ad listener
adropBanner.setListener(new AdropBannerListener() {
@Override
public void onAdReceived(AdropBanner banner) {
// Ad received successfully
}
@Override
public void onAdClicked(AdropBanner banner) {
// Ad clicked
}
@Override
public void onAdFailedToReceive(AdropBanner banner, AdropErrorCode errorCode) {
// Failed to receive ad
}
});
// Load ad
adropBanner.load();
}
}
```
***
### Interstitial Ad (Java)
```java theme={null}
import android.os.Bundle;
import android.widget.Button;
import androidx.appcompat.app.AppCompatActivity;
import io.adrop.ads.interstitial.AdropInterstitialAd;
public class InterstitialActivity extends AppCompatActivity {
private AdropInterstitialAd interstitialAd;
private Button showButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_interstitial);
showButton = findViewById(R.id.show_interstitial_button);
showButton.setEnabled(false);
loadInterstitialAd();
showButton.setOnClickListener(v -> showInterstitialAd());
}
private void loadInterstitialAd() {
interstitialAd = new AdropInterstitialAd(this, "YOUR_UNIT_ID");
interstitialAd.setInterstitialAdListener(new AdropInterstitialAdListener() {
@Override
public void onAdReceived(AdropInterstitialAd ad) {
showButton.setEnabled(true);
}
@Override
public void onAdFailedToReceive(AdropInterstitialAd ad, AdropErrorCode errorCode) {
showButton.setEnabled(false);
}
@Override
public void onAdFailedToShowFullScreen(AdropInterstitialAd ad, AdropErrorCode errorCode) {
// Failed to show ad
}
@Override
public void onAdDidPresentFullScreen(AdropInterstitialAd ad) {
// Ad displayed
}
@Override
public void onAdDidDismissFullScreen(AdropInterstitialAd ad) {
// Ad closed - load new ad
loadInterstitialAd();
}
@Override
public void onAdClicked(AdropInterstitialAd ad) {
// Ad clicked
}
});
interstitialAd.load();
}
private void showInterstitialAd() {
if (interstitialAd != null && interstitialAd.isLoaded()) {
interstitialAd.show(this);
}
}
@Override
protected void onDestroy() {
if (interstitialAd != null) {
interstitialAd.destroy();
}
super.onDestroy();
}
}
```
***
## Advanced Examples
### Dark Mode Support
```kotlin theme={null}
import io.adrop.ads.Adrop
import io.adrop.ads.model.AdropTheme
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Set dark mode
val isDarkMode = resources.configuration.uiMode and
Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
Adrop.setTheme(if (isDarkMode) AdropTheme.DARK else AdropTheme.LIGHT)
}
}
```
***
### Test Mode
```kotlin theme={null}
import io.adrop.ads.Adrop
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// Initialize — use production = false for development/test mode
Adrop.initialize(this, production = !BuildConfig.DEBUG)
}
}
```
***
### User Event Tracking
```kotlin theme={null}
import io.adrop.ads.metrics.AdropMetrics
import io.adrop.ads.metrics.AdropEventParam
class EventTrackingActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_event_tracking)
// Simple event - no parameters
AdropMetrics.sendEvent("app_open")
// Event with parameters
val viewParams = AdropEventParam.Builder()
.putString("item_id", "SKU-123")
.putString("item_name", "Widget")
.putString("item_category", "Electronics")
.putFloat("price", 29.99f)
.build()
AdropMetrics.sendEvent("view_item", viewParams)
// Multi-item event (purchase)
val item1 = AdropEventParam.Builder()
.putString("item_id", "A")
.putString("item_name", "Product A")
.putInt("price", 100)
.putInt("quantity", 1)
.build()
val item2 = AdropEventParam.Builder()
.putString("item_id", "B")
.putString("item_name", "Product B")
.putInt("price", 200)
.putInt("quantity", 2)
.build()
val purchaseParams = AdropEventParam.Builder()
.putString("tx_id", "tx_123")
.putString("currency", "KRW")
.putItems(listOf(item1, item2))
.build()
AdropMetrics.sendEvent("purchase", purchaseParams)
// Sign up event
val signUpParams = AdropEventParam.Builder()
.putString("method", "google")
.build()
AdropMetrics.sendEvent("sign_up", signUpParams)
}
}
```
***
## Related Documentation
Banner ad implementation guide
Native ad implementation guide
Interstitial ad implementation guide
Rewarded ad implementation guide
# Interstitial Ads
Source: https://docs.adrop.io/sdk/android/interstitial
Learn how to implement interstitial ads in your Android app.
## Overview
Interstitial ads are full-screen ads that cover the entire interface of the app. They are best suited for natural transition points in the app, such as game level transitions or content page changes.
### Features
* Full-screen immersive ads
* Remain visible until explicitly closed by the user
* Support for image and video ads
* High visual attention
Use the test unit ID for development: `PUBLIC_TEST_UNIT_ID_INTERSTITIAL`
***
## Implementation Steps
Interstitial ads are implemented in 4 steps:
1. **Initialize** - Create AdropInterstitialAd instance
2. **Set Listener** - Configure listener for ad events
3. **Load Ad** - Request and receive ads
4. **Show Ad** - Display the ad on screen
***
## Basic Implementation
### Creating AdropInterstitialAd Instance
```kotlin Kotlin theme={null}
import io.adrop.ads.interstitial.AdropInterstitialAd
class MainActivity : AppCompatActivity() {
private var interstitialAd: AdropInterstitialAd? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 1. Create interstitial ad instance
interstitialAd = AdropInterstitialAd(this, "YOUR_UNIT_ID")
}
}
```
```java Java theme={null}
import io.adrop.ads.interstitial.AdropInterstitialAd;
public class MainActivity extends AppCompatActivity {
private AdropInterstitialAd interstitialAd;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 1. Create interstitial ad instance
interstitialAd = new AdropInterstitialAd(this, "YOUR_UNIT_ID");
}
}
```
### AdropInterstitialAd Constructor
Android Context object (Activity or Application Context)
Ad unit ID (issued from console)
***
## Setting Listener
To receive ad events, implement and set `AdropInterstitialAdListener`.
```kotlin Kotlin theme={null}
import io.adrop.ads.interstitial.AdropInterstitialAdListener
import io.adrop.ads.model.AdropErrorCode
class MainActivity : AppCompatActivity() {
private var interstitialAd: AdropInterstitialAd? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 1. Create interstitial ad instance
interstitialAd = AdropInterstitialAd(this, "YOUR_UNIT_ID")
// 2. Set listener
interstitialAd?.interstitialAdListener = object : AdropInterstitialAdListener {
override fun onAdReceived(ad: AdropInterstitialAd) {
Log.d("Adrop", "Interstitial ad received")
}
override fun onAdFailedToReceive(ad: AdropInterstitialAd, errorCode: AdropErrorCode) {
Log.e("Adrop", "Interstitial ad failed to receive: $errorCode")
}
override fun onAdImpression(ad: AdropInterstitialAd) {
Log.d("Adrop", "Interstitial ad impression")
}
override fun onAdClicked(ad: AdropInterstitialAd) {
Log.d("Adrop", "Interstitial ad clicked")
}
override fun onAdWillPresentFullScreen(ad: AdropInterstitialAd) {
Log.d("Adrop", "Interstitial ad will present")
}
override fun onAdDidPresentFullScreen(ad: AdropInterstitialAd) {
Log.d("Adrop", "Interstitial ad did present")
}
override fun onAdWillDismissFullScreen(ad: AdropInterstitialAd) {
Log.d("Adrop", "Interstitial ad will dismiss")
}
override fun onAdDidDismissFullScreen(ad: AdropInterstitialAd) {
Log.d("Adrop", "Interstitial ad dismissed")
// Preload next ad
loadInterstitialAd()
}
override fun onAdFailedToShowFullScreen(ad: AdropInterstitialAd, errorCode: AdropErrorCode) {
Log.e("Adrop", "Interstitial ad failed to show: $errorCode")
}
}
// 2-1. Set back button callback listener
interstitialAd?.closeListener = object : AdropInterstitialAdCloseListener {
override fun onBackPressed(ad: AdropInterstitialAd) {
Log.d("Adrop", "Back button pressed")
}
}
// 3. Load ad
loadInterstitialAd()
}
private fun loadInterstitialAd() {
interstitialAd?.load()
}
}
```
```java Java theme={null}
import io.adrop.ads.interstitial.AdropInterstitialAdListener;
import io.adrop.ads.model.AdropErrorCode;
public class MainActivity extends AppCompatActivity {
private AdropInterstitialAd interstitialAd;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 1. Create interstitial ad instance
interstitialAd = new AdropInterstitialAd(this, "YOUR_UNIT_ID");
// 2. Set listener
interstitialAd.setInterstitialAdListener(new AdropInterstitialAdListener() {
@Override
public void onAdReceived(@NonNull AdropInterstitialAd ad) {
Log.d("Adrop", "Interstitial ad received");
}
@Override
public void onAdFailedToReceive(@NonNull AdropInterstitialAd ad, @NonNull AdropErrorCode errorCode) {
Log.e("Adrop", "Interstitial ad failed to receive: " + errorCode);
}
@Override
public void onAdImpression(@NonNull AdropInterstitialAd ad) {
Log.d("Adrop", "Interstitial ad impression");
}
@Override
public void onAdClicked(@NonNull AdropInterstitialAd ad) {
Log.d("Adrop", "Interstitial ad clicked");
}
@Override
public void onAdWillPresentFullScreen(@NonNull AdropInterstitialAd ad) {
Log.d("Adrop", "Interstitial ad will present");
}
@Override
public void onAdDidPresentFullScreen(@NonNull AdropInterstitialAd ad) {
Log.d("Adrop", "Interstitial ad did present");
}
@Override
public void onAdWillDismissFullScreen(@NonNull AdropInterstitialAd ad) {
Log.d("Adrop", "Interstitial ad will dismiss");
}
@Override
public void onAdDidDismissFullScreen(@NonNull AdropInterstitialAd ad) {
Log.d("Adrop", "Interstitial ad dismissed");
// Preload next ad
loadInterstitialAd();
}
@Override
public void onAdFailedToShowFullScreen(@NonNull AdropInterstitialAd ad, @NonNull AdropErrorCode errorCode) {
Log.e("Adrop", "Interstitial ad failed to show: " + errorCode);
}
});
// 2-1. Set back button callback listener
interstitialAd.setCloseListener(new AdropInterstitialAdCloseListener() {
@Override
public void onBackPressed(@NonNull AdropInterstitialAd ad) {
Log.d("Adrop", "Back button pressed");
}
});
// 3. Load ad
loadInterstitialAd();
}
private void loadInterstitialAd() {
interstitialAd.load();
}
}
```
***
## Loading Ad
Call the `load()` method to request an ad.
```kotlin Kotlin theme={null}
private fun loadInterstitialAd() {
interstitialAd?.load()
}
```
```java Java theme={null}
private void loadInterstitialAd() {
interstitialAd.load();
}
```
The `onAdReceived` callback is called when the ad is successfully loaded, and `onAdFailedToReceive` is called when loading fails.
***
## Showing Ad
Call the `show()` method to display the ad after it's loaded.
### isLoaded Property
A property to check if the ad is loaded. It's recommended to check this value before calling `show()`.
```kotlin Kotlin theme={null}
private fun showInterstitialAd() {
if (interstitialAd?.isLoaded == true) {
interstitialAd?.show(this)
} else {
Log.d("Adrop", "Ad is not loaded yet")
}
}
```
```java Java theme={null}
private void showInterstitialAd() {
if (interstitialAd != null && interstitialAd.isLoaded()) {
interstitialAd.show(this);
} else {
Log.d("Adrop", "Ad is not loaded yet");
}
}
```
### show Method
Activity object to display the ad
***
## AdropInterstitialAd Properties
### unitId
Returns the ad unit ID.
```kotlin Kotlin theme={null}
val unitId: String = interstitialAd?.unitId ?: ""
```
```java Java theme={null}
String unitId = interstitialAd.getUnitId();
```
### isLoaded
Returns whether the ad is loaded.
```kotlin Kotlin theme={null}
val isLoaded: Boolean = interstitialAd?.isLoaded ?: false
```
```java Java theme={null}
boolean isLoaded = interstitialAd.isLoaded();
```
### creativeId
Returns the creative ID of the currently loaded ad.
```kotlin Kotlin theme={null}
val creativeId: String = interstitialAd?.creativeId ?: ""
```
```java Java theme={null}
String creativeId = interstitialAd.getCreativeId();
```
### txId
Returns the transaction ID of the currently loaded ad.
```kotlin Kotlin theme={null}
val txId: String = interstitialAd?.txId ?: ""
```
```java Java theme={null}
String txId = interstitialAd.getTxId();
```
### campaignId
Returns the campaign ID of the currently loaded ad.
```kotlin Kotlin theme={null}
val campaignId: String = interstitialAd?.campaignId ?: ""
```
```java Java theme={null}
String campaignId = interstitialAd.getCampaignId();
```
### isBackfilled
Returns whether the currently loaded ad is a backfill ad.
```kotlin Kotlin theme={null}
val isBackfilled: Boolean = interstitialAd?.isBackfilled ?: false
```
```java Java theme={null}
boolean isBackfilled = interstitialAd.isBackfilled();
```
### close()
Programmatically closes the interstitial ad.
```kotlin Kotlin theme={null}
interstitialAd?.close()
```
```java Java theme={null}
interstitialAd.close();
```
***
## AdropInterstitialAdListener Methods
### Required Methods
Called when ad is successfully received. You can call `show()` to display the ad from this point.
Called when ad fails to load. Check the error code for the failure reason.
### Optional Methods
Called when an ad impression is recorded.
Called when the user clicks the ad.
Called just before the interstitial ad is displayed. You can pause game logic here.
Called immediately after the interstitial ad is displayed on screen.
Called just before the interstitial ad is closed.
Called immediately after the interstitial ad is closed. Good time to preload the next ad.
Called when ad fails to show. Check the error code for the failure reason.
***
## Back Button Callback
You can receive a callback when the user presses the back button while an interstitial ad is displayed by setting `AdropInterstitialAdCloseListener`.
### AdropInterstitialAdCloseListener
Called when the user presses the back button while the interstitial ad is displayed.
### Usage
```kotlin Kotlin theme={null}
import io.adrop.ads.interstitial.AdropInterstitialAd
import io.adrop.ads.interstitial.AdropInterstitialAdCloseListener
// 1. Define closeListener
private val closeListener = object : AdropInterstitialAdCloseListener {
override fun onBackPressed(ad: AdropInterstitialAd) {
// Called when the user presses the back button
Log.d("InterstitialAd", "Back button pressed")
// Call close() to dismiss the ad
ad.close()
}
}
// 2. Set closeListener when creating the interstitial ad
val interstitialAd = AdropInterstitialAd(context, "YOUR_UNIT_ID").apply {
interstitialAdListener = yourAdListener
closeListener = this@YourActivity.closeListener
load()
}
```
```java Java theme={null}
import io.adrop.ads.interstitial.AdropInterstitialAd;
import io.adrop.ads.interstitial.AdropInterstitialAdCloseListener;
import io.adrop.ads.interstitial.AdropInterstitialAdListener;
// 1. Define closeListener
private final AdropInterstitialAdCloseListener closeListener = new AdropInterstitialAdCloseListener() {
@Override
public void onBackPressed(@NonNull AdropInterstitialAd ad) {
// Called when the user presses the back button
Log.d("InterstitialAd", "Back button pressed");
// Call close() to dismiss the ad
ad.close();
}
};
// 2. Set closeListener when creating the interstitial ad
AdropInterstitialAd interstitialAd = new AdropInterstitialAd(context, "YOUR_UNIT_ID");
interstitialAd.setInterstitialAdListener(yourAdListener);
interstitialAd.setCloseListener(closeListener); // New
interstitialAd.load();
```
### Behavior
* **Back button callback**: When the user presses the back button, `closeListener.onBackPressed()` is called first.
* **Auto-close condition**: If more than 30 seconds have passed since the ad was displayed, or if there is no WebView, the ad is automatically closed when the back button is pressed.
* **Manual close**: You can programmatically close the ad at any time by calling `ad.close()`.
* **If closeListener is not set**: The existing behavior is maintained (auto-close after 30 seconds only).
### Android 15+ Compatibility
On Android 13 (Tiramisu) and above, `OnBackInvokedCallback` is used to support Predictive Back. It works correctly even if your app has `enableOnBackInvokedCallback="true"` set.
```xml theme={null}
```
***
## Lifecycle Management
### destroy Method
You must call `destroy()` to release resources when the Activity or Fragment is destroyed.
```kotlin Kotlin theme={null}
override fun onDestroy() {
super.onDestroy()
interstitialAd?.destroy()
interstitialAd = null
}
```
```java Java theme={null}
@Override
protected void onDestroy() {
super.onDestroy();
if (interstitialAd != null) {
interstitialAd.destroy();
interstitialAd = null;
}
}
```
Always call `destroy()` to prevent memory leaks.
***
## Preloading Strategy
It's recommended to preload ads for better user experience.
### Basic Preloading
```kotlin Kotlin theme={null}
class GameActivity : AppCompatActivity() {
private var interstitialAd: AdropInterstitialAd? = null
private var isAdReady = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_game)
// Preload on screen entry
preloadInterstitialAd()
}
private fun preloadInterstitialAd() {
interstitialAd = AdropInterstitialAd(this, "YOUR_UNIT_ID")
interstitialAd?.interstitialAdListener = object : AdropInterstitialAdListener {
override fun onAdReceived(ad: AdropInterstitialAd) {
isAdReady = true
Log.d("Adrop", "Interstitial ad ready")
}
override fun onAdFailedToReceive(ad: AdropInterstitialAd, errorCode: AdropErrorCode) {
isAdReady = false
Log.e("Adrop", "Interstitial ad load failed: $errorCode")
}
override fun onAdDidDismissFullScreen(ad: AdropInterstitialAd) {
isAdReady = false
// Preload next ad
preloadInterstitialAd()
}
}
interstitialAd?.load()
}
private fun onGameLevelComplete() {
// Show immediately on level complete
if (isAdReady) {
interstitialAd?.show(this)
}
}
}
```
```java Java theme={null}
public class GameActivity extends AppCompatActivity {
private AdropInterstitialAd interstitialAd;
private boolean isAdReady = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_game);
// Preload on screen entry
preloadInterstitialAd();
}
private void preloadInterstitialAd() {
interstitialAd = new AdropInterstitialAd(this, "YOUR_UNIT_ID");
interstitialAd.setInterstitialAdListener(new AdropInterstitialAdListener() {
@Override
public void onAdReceived(@NonNull AdropInterstitialAd ad) {
isAdReady = true;
Log.d("Adrop", "Interstitial ad ready");
}
@Override
public void onAdFailedToReceive(@NonNull AdropInterstitialAd ad, @NonNull AdropErrorCode errorCode) {
isAdReady = false;
Log.e("Adrop", "Interstitial ad load failed: " + errorCode);
}
@Override
public void onAdDidDismissFullScreen(@NonNull AdropInterstitialAd ad) {
isAdReady = false;
// Preload next ad
preloadInterstitialAd();
}
});
interstitialAd.load();
}
private void onGameLevelComplete() {
// Show immediately on level complete
if (isAdReady) {
interstitialAd.show(this);
}
}
}
```
### Reload After Dismiss
Preload the next ad in the `onAdDidDismissFullScreen` callback after the ad is closed.
```kotlin Kotlin theme={null}
override fun onAdDidDismissFullScreen(ad: AdropInterstitialAd) {
Log.d("Adrop", "Interstitial ad dismissed")
// Immediately load next ad
interstitialAd?.load()
}
```
```java Java theme={null}
@Override
public void onAdDidDismissFullScreen(@NonNull AdropInterstitialAd ad) {
Log.d("Adrop", "Interstitial ad dismissed");
// Immediately load next ad
interstitialAd.load();
}
```
***
## Best Practices
### 1. Appropriate Display Timing
Show ads at natural transition points in the app.
```kotlin Kotlin theme={null}
// Good: Game level transition
fun onLevelComplete() {
saveProgress()
showInterstitialAd()
loadNextLevel()
}
// Good: Content reading complete
fun onArticleFinished() {
showInterstitialAd()
}
// Bad: During user action
fun onButtonClick() {
showInterstitialAd() // Disrupts user experience
performAction()
}
```
```java Java theme={null}
// Good: Game level transition
private void onLevelComplete() {
saveProgress();
showInterstitialAd();
loadNextLevel();
}
// Good: Content reading complete
private void onArticleFinished() {
showInterstitialAd();
}
// Bad: During user action
private void onButtonClick() {
showInterstitialAd(); // Disrupts user experience
performAction();
}
```
### 2. Error Handling
Implement handling for ad load failures.
```kotlin Kotlin theme={null}
override fun onAdFailedToReceive(ad: AdropInterstitialAd, errorCode: AdropErrorCode) {
when (errorCode) {
AdropErrorCode.ERROR_CODE_NETWORK -> {
Log.e("Adrop", "Network error: try again later")
retryAfterDelay()
}
AdropErrorCode.ERROR_CODE_AD_NO_FILL -> {
Log.w("Adrop", "No ads available")
continueWithoutAd()
}
else -> {
Log.e("Adrop", "Ad load failed: $errorCode")
}
}
}
private fun retryAfterDelay() {
Handler(Looper.getMainLooper()).postDelayed({
interstitialAd?.load()
}, 30000) // Retry after 30 seconds
}
private fun continueWithoutAd() {
// Continue without ad
proceedToNextScreen()
}
```
```java Java theme={null}
@Override
public void onAdFailedToReceive(@NonNull AdropInterstitialAd ad, @NonNull AdropErrorCode errorCode) {
switch (errorCode) {
case ERROR_CODE_NETWORK:
Log.e("Adrop", "Network error: try again later");
retryAfterDelay();
break;
case ERROR_CODE_AD_NO_FILL:
Log.w("Adrop", "No ads available");
continueWithoutAd();
break;
default:
Log.e("Adrop", "Ad load failed: " + errorCode);
break;
}
}
private void retryAfterDelay() {
new Handler(Looper.getMainLooper()).postDelayed(() -> {
interstitialAd.load();
}, 30000); // Retry after 30 seconds
}
private void continueWithoutAd() {
// Continue without ad
proceedToNextScreen();
}
```
### 3. Frequency Capping
Don't show ads too frequently.
```kotlin Kotlin theme={null}
class AdFrequencyManager {
private var lastAdShownTime: Long = 0
private val minimumInterval = 180_000L // 3 minutes
fun canShowAd(): Boolean {
val currentTime = System.currentTimeMillis()
return currentTime - lastAdShownTime >= minimumInterval
}
fun recordAdShown() {
lastAdShownTime = System.currentTimeMillis()
}
}
// Usage example
class MainActivity : AppCompatActivity() {
private val frequencyManager = AdFrequencyManager()
private fun showInterstitialIfAllowed() {
if (!frequencyManager.canShowAd()) {
Log.d("Adrop", "Ad display interval too short")
return
}
interstitialAd?.show(this)
}
// Call in listener
override fun onAdDidPresentFullScreen(ad: AdropInterstitialAd) {
frequencyManager.recordAdShown()
}
}
```
```java Java theme={null}
public class AdFrequencyManager {
private long lastAdShownTime = 0;
private final long minimumInterval = 180_000L; // 3 minutes
public boolean canShowAd() {
long currentTime = System.currentTimeMillis();
return currentTime - lastAdShownTime >= minimumInterval;
}
public void recordAdShown() {
lastAdShownTime = System.currentTimeMillis();
}
}
// Usage example
public class MainActivity extends AppCompatActivity {
private final AdFrequencyManager frequencyManager = new AdFrequencyManager();
private void showInterstitialIfAllowed() {
if (!frequencyManager.canShowAd()) {
Log.d("Adrop", "Ad display interval too short");
return;
}
interstitialAd.show(this);
}
// Call in listener
@Override
public void onAdDidPresentFullScreen(@NonNull AdropInterstitialAd ad) {
frequencyManager.recordAdShown();
}
}
```
### 4. Game Pause Handling
Pause games or animations when showing interstitial ads.
```kotlin Kotlin theme={null}
override fun onAdWillPresentFullScreen(ad: AdropInterstitialAd) {
// Pause game
pauseGame()
// Stop background music
pauseBackgroundMusic()
}
override fun onAdDidDismissFullScreen(ad: AdropInterstitialAd) {
// Resume game
resumeGame()
// Resume background music
resumeBackgroundMusic()
}
```
```java Java theme={null}
@Override
public void onAdWillPresentFullScreen(@NonNull AdropInterstitialAd ad) {
// Pause game
pauseGame();
// Stop background music
pauseBackgroundMusic();
}
@Override
public void onAdDidDismissFullScreen(@NonNull AdropInterstitialAd ad) {
// Resume game
resumeGame();
// Resume background music
resumeBackgroundMusic();
}
```
***
## Testing
### Using Test Unit ID
Use test unit IDs during development.
```kotlin Kotlin theme={null}
import io.adrop.ads.AdropAds
class MainActivity : AppCompatActivity() {
private val unitId = if (BuildConfig.DEBUG) {
"PUBLIC_TEST_UNIT_ID_INTERSTITIAL"
} else {
"YOUR_PRODUCTION_UNIT_ID"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
interstitialAd = AdropInterstitialAd(this, unitId)
}
}
```
```java Java theme={null}
public class MainActivity extends AppCompatActivity {
private final String unitId = BuildConfig.DEBUG ?
"PUBLIC_TEST_UNIT_ID_INTERSTITIAL" :
"YOUR_PRODUCTION_UNIT_ID";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
interstitialAd = new AdropInterstitialAd(this, unitId);
}
}
```
### Verify Ad Load
Verify that ads are loading correctly.
```kotlin Kotlin theme={null}
override fun onAdReceived(ad: AdropInterstitialAd) {
Log.d("Adrop", "Interstitial ad load success")
Log.d("Adrop", "Unit ID: ${ad.unitId}")
Log.d("Adrop", "Creative ID: ${ad.creativeId}")
Log.d("Adrop", "Is Backfilled: ${ad.isBackfilled}")
}
override fun onAdFailedToReceive(ad: AdropInterstitialAd, errorCode: AdropErrorCode) {
Log.e("Adrop", "Interstitial ad load failed: $errorCode")
if (BuildConfig.DEBUG) {
Toast.makeText(this, "Ad load failed: $errorCode", Toast.LENGTH_SHORT).show()
}
}
```
```java Java theme={null}
@Override
public void onAdReceived(@NonNull AdropInterstitialAd ad) {
Log.d("Adrop", "Interstitial ad load success");
Log.d("Adrop", "Unit ID: " + ad.getUnitId());
Log.d("Adrop", "Creative ID: " + ad.getCreativeId());
Log.d("Adrop", "Is Backfilled: " + ad.isBackfilled());
}
@Override
public void onAdFailedToReceive(@NonNull AdropInterstitialAd ad, @NonNull AdropErrorCode errorCode) {
Log.e("Adrop", "Interstitial ad load failed: " + errorCode);
if (BuildConfig.DEBUG) {
Toast.makeText(this, "Ad load failed: " + errorCode, Toast.LENGTH_SHORT).show();
}
}
```
***
## Complete Implementation Example
```kotlin Kotlin theme={null}
import android.os.Bundle
import android.util.Log
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import io.adrop.ads.interstitial.AdropInterstitialAd
import io.adrop.ads.interstitial.AdropInterstitialAdListener
import io.adrop.ads.model.AdropErrorCode
class InterstitialAdActivity : AppCompatActivity() {
private var interstitialAd: AdropInterstitialAd? = null
private lateinit var loadButton: Button
private lateinit var showButton: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_interstitial_ad)
loadButton = findViewById(R.id.btn_load_ad)
showButton = findViewById(R.id.btn_show_ad)
// Set initial button state
showButton.isEnabled = false
// Initialize interstitial ad
setupInterstitialAd()
// Button click listeners
loadButton.setOnClickListener {
loadInterstitialAd()
}
showButton.setOnClickListener {
showInterstitialAd()
}
// Auto-load first ad
loadInterstitialAd()
}
private fun setupInterstitialAd() {
val unitId = if (BuildConfig.DEBUG) {
"PUBLIC_TEST_UNIT_ID_INTERSTITIAL"
} else {
"YOUR_PRODUCTION_UNIT_ID"
}
interstitialAd = AdropInterstitialAd(this, unitId)
interstitialAd?.interstitialAdListener = object : AdropInterstitialAdListener {
override fun onAdReceived(ad: AdropInterstitialAd) {
Log.d(TAG, "Interstitial ad received")
showButton.isEnabled = true
loadButton.isEnabled = false
Toast.makeText(
this@InterstitialAdActivity,
"Ad is ready",
Toast.LENGTH_SHORT
).show()
}
override fun onAdFailedToReceive(ad: AdropInterstitialAd, errorCode: AdropErrorCode) {
Log.e(TAG, "Interstitial ad failed to receive: $errorCode")
showButton.isEnabled = false
loadButton.isEnabled = true
Toast.makeText(
this@InterstitialAdActivity,
"Ad load failed: $errorCode",
Toast.LENGTH_SHORT
).show()
}
override fun onAdImpression(ad: AdropInterstitialAd) {
Log.d(TAG, "Interstitial ad impression")
}
override fun onAdClicked(ad: AdropInterstitialAd) {
Log.d(TAG, "Interstitial ad clicked")
}
override fun onAdWillPresentFullScreen(ad: AdropInterstitialAd) {
Log.d(TAG, "Interstitial ad will present")
}
override fun onAdDidPresentFullScreen(ad: AdropInterstitialAd) {
Log.d(TAG, "Interstitial ad did present")
}
override fun onAdWillDismissFullScreen(ad: AdropInterstitialAd) {
Log.d(TAG, "Interstitial ad will dismiss")
}
override fun onAdDidDismissFullScreen(ad: AdropInterstitialAd) {
Log.d(TAG, "Interstitial ad dismissed")
showButton.isEnabled = false
loadButton.isEnabled = true
Toast.makeText(
this@InterstitialAdActivity,
"Ad closed",
Toast.LENGTH_SHORT
).show()
// Preload next ad
loadInterstitialAd()
}
override fun onAdFailedToShowFullScreen(ad: AdropInterstitialAd, errorCode: AdropErrorCode) {
Log.e(TAG, "Interstitial ad failed to show: $errorCode")
showButton.isEnabled = false
loadButton.isEnabled = true
Toast.makeText(
this@InterstitialAdActivity,
"Ad show failed: $errorCode",
Toast.LENGTH_SHORT
).show()
}
}
}
private fun loadInterstitialAd() {
Log.d(TAG, "Loading interstitial ad")
loadButton.isEnabled = false
interstitialAd?.load()
}
private fun showInterstitialAd() {
if (interstitialAd?.isLoaded == true) {
Log.d(TAG, "Showing interstitial ad")
interstitialAd?.show(this)
} else {
Log.w(TAG, "Ad is not loaded yet")
Toast.makeText(this, "Ad is not ready", Toast.LENGTH_SHORT).show()
}
}
override fun onDestroy() {
super.onDestroy()
interstitialAd?.destroy()
interstitialAd = null
}
companion object {
private const val TAG = "InterstitialAdActivity"
}
}
```
```java Java theme={null}
import android.os.Bundle;
import android.util.Log;
import android.widget.Button;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import io.adrop.ads.interstitial.AdropInterstitialAd;
import io.adrop.ads.interstitial.AdropInterstitialAdListener;
import io.adrop.ads.model.AdropErrorCode;
public class InterstitialAdActivity extends AppCompatActivity {
private static final String TAG = "InterstitialAdActivity";
private AdropInterstitialAd interstitialAd;
private Button loadButton;
private Button showButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_interstitial_ad);
loadButton = findViewById(R.id.btn_load_ad);
showButton = findViewById(R.id.btn_show_ad);
// Set initial button state
showButton.setEnabled(false);
// Initialize interstitial ad
setupInterstitialAd();
// Button click listeners
loadButton.setOnClickListener(v -> loadInterstitialAd());
showButton.setOnClickListener(v -> showInterstitialAd());
// Auto-load first ad
loadInterstitialAd();
}
private void setupInterstitialAd() {
String unitId = BuildConfig.DEBUG ?
"PUBLIC_TEST_UNIT_ID_INTERSTITIAL" :
"YOUR_PRODUCTION_UNIT_ID";
interstitialAd = new AdropInterstitialAd(this, unitId);
interstitialAd.setInterstitialAdListener(new AdropInterstitialAdListener() {
@Override
public void onAdReceived(@NonNull AdropInterstitialAd ad) {
Log.d(TAG, "Interstitial ad received");
showButton.setEnabled(true);
loadButton.setEnabled(false);
Toast.makeText(
InterstitialAdActivity.this,
"Ad is ready",
Toast.LENGTH_SHORT
).show();
}
@Override
public void onAdFailedToReceive(@NonNull AdropInterstitialAd ad, @NonNull AdropErrorCode errorCode) {
Log.e(TAG, "Interstitial ad failed to receive: " + errorCode);
showButton.setEnabled(false);
loadButton.setEnabled(true);
Toast.makeText(
InterstitialAdActivity.this,
"Ad load failed: " + errorCode,
Toast.LENGTH_SHORT
).show();
}
@Override
public void onAdImpression(@NonNull AdropInterstitialAd ad) {
Log.d(TAG, "Interstitial ad impression");
}
@Override
public void onAdClicked(@NonNull AdropInterstitialAd ad) {
Log.d(TAG, "Interstitial ad clicked");
}
@Override
public void onAdWillPresentFullScreen(@NonNull AdropInterstitialAd ad) {
Log.d(TAG, "Interstitial ad will present");
}
@Override
public void onAdDidPresentFullScreen(@NonNull AdropInterstitialAd ad) {
Log.d(TAG, "Interstitial ad did present");
}
@Override
public void onAdWillDismissFullScreen(@NonNull AdropInterstitialAd ad) {
Log.d(TAG, "Interstitial ad will dismiss");
}
@Override
public void onAdDidDismissFullScreen(@NonNull AdropInterstitialAd ad) {
Log.d(TAG, "Interstitial ad dismissed");
showButton.setEnabled(false);
loadButton.setEnabled(true);
Toast.makeText(
InterstitialAdActivity.this,
"Ad closed",
Toast.LENGTH_SHORT
).show();
// Preload next ad
loadInterstitialAd();
}
@Override
public void onAdFailedToShowFullScreen(@NonNull AdropInterstitialAd ad, @NonNull AdropErrorCode errorCode) {
Log.e(TAG, "Interstitial ad failed to show: " + errorCode);
showButton.setEnabled(false);
loadButton.setEnabled(true);
Toast.makeText(
InterstitialAdActivity.this,
"Ad show failed: " + errorCode,
Toast.LENGTH_SHORT
).show();
}
});
}
private void loadInterstitialAd() {
Log.d(TAG, "Loading interstitial ad");
loadButton.setEnabled(false);
interstitialAd.load();
}
private void showInterstitialAd() {
if (interstitialAd != null && interstitialAd.isLoaded()) {
Log.d(TAG, "Showing interstitial ad");
interstitialAd.show(this);
} else {
Log.w(TAG, "Ad is not loaded yet");
Toast.makeText(this, "Ad is not ready", Toast.LENGTH_SHORT).show();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (interstitialAd != null) {
interstitialAd.destroy();
interstitialAd = null;
}
}
}
```
***
## Troubleshooting
* Verify SDK is initialized
* Check unit ID is correct
* Check network connection
* Verify AndroidManifest.xml has internet permission
* Check the error code to identify the issue
* `ERROR_CODE_AD_NO_FILL`: No ad inventory, try again later
* `ERROR_CODE_NETWORK`: Check network connection
* `ERROR_CODE_INVALID_UNIT`: Verify unit ID
* Ensure `show()` is called after `onAdReceived` callback
* Check `isLoaded` property is `true`
* Verify Activity is valid
* Ensure another interstitial ad is not already showing
* Verify `destroy()` is called in `onDestroy()`
* Check Context reference is managed with WeakReference
* Ensure listener is set to null
***
## Backfill Ads
When backfill ads are enabled, backfill ads are automatically loaded when direct ads are unavailable. Use the `isBackfilled` property to check if the ad is a backfill ad.
```kotlin Kotlin theme={null}
interstitialAd.interstitialAdListener = object : AdropInterstitialAdListener {
override fun onAdReceived(ad: AdropInterstitialAd) {
if (ad.isBackfilled) {
println("Backfill ad loaded")
} else {
println("Direct ad loaded")
}
}
override fun onAdFailedToReceive(ad: AdropInterstitialAd, errorCode: AdropErrorCode) {
when (errorCode) {
AdropErrorCode.ERROR_CODE_AD_NO_FILL -> {
println("No direct ad, requesting backfill...")
}
AdropErrorCode.ERROR_CODE_AD_BACKFILL_NO_FILL -> {
println("Backfill ad also not available")
}
else -> {
println("Ad load failed: $errorCode")
}
}
}
}
```
```java Java theme={null}
interstitialAd.setInterstitialAdListener(new AdropInterstitialAdListener() {
@Override
public void onAdReceived(AdropInterstitialAd ad) {
if (ad.isBackfilled()) {
System.out.println("Backfill ad loaded");
} else {
System.out.println("Direct ad loaded");
}
}
@Override
public void onAdFailedToReceive(AdropInterstitialAd ad, AdropErrorCode errorCode) {
if (errorCode == AdropErrorCode.ERROR_CODE_AD_NO_FILL) {
System.out.println("No direct ad, requesting backfill...");
} else if (errorCode == AdropErrorCode.ERROR_CODE_AD_BACKFILL_NO_FILL) {
System.out.println("Backfill ad also not available");
} else {
System.out.println("Ad load failed: " + errorCode);
}
}
});
```
To use backfill ads, add the `io.adrop:adrop-ads-backfill` dependency. See [Getting Started](/sdk/android/overview).
***
## Next Steps
Improve user engagement with rewarded ads
Implement banner ads
User properties and contextual targeting
API reference documentation
# Native Ads
Source: https://docs.adrop.io/sdk/android/native
Learn how to implement native ads in Android.
## Overview
Native ads are an ad format that allows you to freely customize the ad UI to match your app's design. You can provide a natural user experience by individually placing elements such as ad creatives (images, videos), headline, body, and CTA buttons.
### Key Features
* **Complete UI Customization**: Freely configure ad layout to match your app's design system
* **Various Media Support**: Image and video ad creative support
* **Flexible Click Area Settings**: Full click or individual element click handling
* **Profile Information Display**: Advertiser profile logo, name, and link support
***
## Classes and Interfaces
### AdropNativeAd
Main class for loading and managing native ads.
Ad unit ID (issued from console)
ID for contextual targeting (optional)
Listener to handle ad events
Whether to use custom click handling
#### Key Properties
| Property | Type | Description |
| - | - | - |
| `headline` | `String` | Ad headline |
| `body` | `String` | Ad body text |
| `icon` | `String` | Icon image URL |
| `cover` | `String` | Cover image URL |
| `advertiser` | `String` | **Deprecated.** Use `profile.displayName` instead. |
| `advertiserURL` | `String` | **Deprecated.** Use `profile.link` instead. |
| `accountTag` | `JSONObject` | **Deprecated.** No longer supported. |
| `creativeTag` | `JSONObject` | **Deprecated.** No longer supported. |
| `callToAction` | `String` | Call-to-action text (e.g., "Learn More") |
| `profile` | `AdropNativeAdProfile` | Advertiser profile information |
| `isLoaded` | `Boolean` | Whether ad loading is complete |
| `isDestroyed` | `Boolean` | Whether ad is destroyed |
### AdropNativeAdView
Container view for displaying native ads.
Whether to enable entire area click. When set to `true`, click events occur on the entire ad view area.
#### Key Methods
| Method | Description |
| - | - |
| `setIconView(view, listener)` | Set icon image view (ImageView type) |
| `setHeadLineView(view, listener)` | Set headline text view (TextView type) |
| `setBodyView(view)` | Set body text view (TextView type) |
| `setMediaView(view)` | Set media container view (AdropMediaView type) |
| `setAdvertiserView(view, listener)` | Set advertiser text view (TextView type) |
| `setCallToActionView(view)` | Set CTA button or text view |
| `setProfileLogoView(view, listener)` | Set profile logo image view (ImageView type) |
| `setProfileNameView(view, listener)` | Set profile name text view (TextView type) |
| `setNativeAd(ad)` | Bind ad data to view |
| `destroy()` | Release resources |
### AdropMediaView
Media container view for displaying ad images or videos.
```xml theme={null}
```
For video creatives, `AdropMediaView` must be bound through `AdropNativeAdView.setMediaView(...)`. Reading `ad.asset` and playing the URL with your own player (`ExoPlayer`, `MediaPlayer`, or any third-party library) bypasses the SDK's video tracking pipeline, so **VTR is not measured** and `onAdVideoStart` / `onAdVideoEnd` never fire. See [Video Tracking and VTR](#video-tracking-and-vtr).
### AdropNativeAdListener
Interface for handling ad events.
```kotlin theme={null}
interface AdropNativeAdListener {
fun onAdReceived(ad: AdropNativeAd)
fun onAdClicked(ad: AdropNativeAd)
fun onAdFailedToReceive(ad: AdropNativeAd, errorCode: AdropErrorCode)
fun onAdImpression(ad: AdropNativeAd) // Optional implementation
fun onAdVideoStart(ad: AdropNativeAd) // Optional implementation
fun onAdVideoEnd(ad: AdropNativeAd) // Optional implementation
}
```
### AdropNativeAdProfile
Data class containing advertiser profile information.
| Property | Type | Description |
| - | - | - |
| `displayLogo` | `String` | Profile logo image URL |
| `displayName` | `String` | Profile display name |
| `link` | `String` | Profile link URL |
***
## Implementation Guide
### 1. XML Layout Setup
Define the layout to display native ads. Use `AdropNativeAdView` as the root and place ad elements inside.
```xml res/layout/native_ad_layout.xml theme={null}
```
### 2. Load and Display Ad (Kotlin)
```kotlin Activity theme={null}
import android.os.Bundle
import android.util.Log
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import com.bumptech.glide.Glide
import io.adrop.ads.model.AdropErrorCode
import io.adrop.ads.nativeAd.AdropMediaView
import io.adrop.ads.nativeAd.AdropNativeAd
import io.adrop.ads.nativeAd.AdropNativeAdListener
import io.adrop.ads.nativeAd.AdropNativeAdView
class NativeAdActivity : AppCompatActivity() {
private lateinit var nativeAd: AdropNativeAd
private lateinit var nativeAdView: AdropNativeAdView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_native_ad)
nativeAdView = findViewById(R.id.native_ad_view)
loadNativeAd()
}
private fun loadNativeAd() {
// 1. Create native ad instance
nativeAd = AdropNativeAd(
context = this,
unitId = "YOUR_UNIT_ID" // Replace with actual unit ID
)
// 2. Set listener
nativeAd.listener = object : AdropNativeAdListener {
override fun onAdReceived(ad: AdropNativeAd) {
Log.d("Adrop", "Ad received successfully")
populateNativeAdView(ad)
}
override fun onAdFailedToReceive(ad: AdropNativeAd, errorCode: AdropErrorCode) {
Log.e("Adrop", "Ad failed to receive: $errorCode")
}
override fun onAdClicked(ad: AdropNativeAd) {
Log.d("Adrop", "Ad clicked")
}
override fun onAdImpression(ad: AdropNativeAd) {
Log.d("Adrop", "Ad impression")
}
override fun onAdVideoStart(ad: AdropNativeAd) {
Log.d("Adrop", "Ad video started")
}
override fun onAdVideoEnd(ad: AdropNativeAd) {
Log.d("Adrop", "Ad video ended")
}
}
// 3. Load ad
nativeAd.load()
}
private fun populateNativeAdView(ad: AdropNativeAd) {
// Set profile
val profileLogoView = findViewById(R.id.ad_profile_logo)
Glide.with(this).load(ad.profile.displayLogo).into(profileLogoView)
nativeAdView.setProfileLogoView(profileLogoView)
val profileNameView = findViewById(R.id.ad_profile_name)
profileNameView.text = ad.profile.displayName
nativeAdView.setProfileNameView(profileNameView)
// Set headline
val headlineView = findViewById(R.id.ad_headline)
headlineView.text = ad.headline
nativeAdView.setHeadLineView(headlineView)
// Set media
val mediaView = findViewById(R.id.ad_media)
nativeAdView.setMediaView(mediaView)
// Set body
val bodyView = findViewById(R.id.ad_body)
bodyView.text = ad.body
nativeAdView.setBodyView(bodyView)
// Set advertiser
val advertiserView = findViewById(R.id.ad_advertiser)
advertiserView.text = ad.profile.displayName
nativeAdView.setAdvertiserView(advertiserView)
// Set CTA button
val ctaView = findViewById
### 3. Load and Display Ad (Java)
```java Java theme={null}
import android.os.Bundle;
import android.util.Log;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import com.bumptech.glide.Glide;
import io.adrop.ads.model.AdropErrorCode;
import io.adrop.ads.nativeAd.AdropMediaView;
import io.adrop.ads.nativeAd.AdropNativeAd;
import io.adrop.ads.nativeAd.AdropNativeAdListener;
import io.adrop.ads.nativeAd.AdropNativeAdView;
public class NativeAdActivity extends AppCompatActivity {
private AdropNativeAd nativeAd;
private AdropNativeAdView nativeAdView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_native_ad);
nativeAdView = findViewById(R.id.native_ad_view);
loadNativeAd();
}
private void loadNativeAd() {
// 1. Create native ad instance
nativeAd = new AdropNativeAd(
this,
"YOUR_UNIT_ID", // Replace with actual unit ID
null // contextId (optional)
);
// 2. Set listener
nativeAd.setListener(new AdropNativeAdListener() {
@Override
public void onAdReceived(AdropNativeAd ad) {
Log.d("Adrop", "Ad received successfully");
populateNativeAdView(ad);
}
@Override
public void onAdFailedToReceive(AdropNativeAd ad, AdropErrorCode errorCode) {
Log.e("Adrop", "Ad failed to receive: " + errorCode);
}
@Override
public void onAdClicked(AdropNativeAd ad) {
Log.d("Adrop", "Ad clicked");
}
@Override
public void onAdImpression(AdropNativeAd ad) {
Log.d("Adrop", "Ad impression");
}
@Override
public void onAdVideoStart(AdropNativeAd ad) {
Log.d("Adrop", "Ad video started");
}
@Override
public void onAdVideoEnd(AdropNativeAd ad) {
Log.d("Adrop", "Ad video ended");
}
});
// 3. Load ad
nativeAd.load();
}
private void populateNativeAdView(AdropNativeAd ad) {
// Set profile logo
ImageView profileLogoView = findViewById(R.id.ad_profile_logo);
Glide.with(this).load(ad.getProfile().getDisplayLogo()).into(profileLogoView);
nativeAdView.setProfileLogoView(profileLogoView, null);
// Set profile name
TextView profileNameView = findViewById(R.id.ad_profile_name);
profileNameView.setText(ad.getProfile().getDisplayName());
nativeAdView.setProfileNameView(profileNameView, null);
// Set headline
TextView headlineView = findViewById(R.id.ad_headline);
headlineView.setText(ad.getHeadline());
nativeAdView.setHeadLineView(headlineView, null);
// Set media
AdropMediaView mediaView = findViewById(R.id.ad_media);
nativeAdView.setMediaView(mediaView);
// Set body
TextView bodyView = findViewById(R.id.ad_body);
bodyView.setText(ad.getBody());
nativeAdView.setBodyView(bodyView);
// Set advertiser
TextView advertiserView = findViewById(R.id.ad_advertiser);
advertiserView.setText(ad.getProfile().getDisplayName());
nativeAdView.setAdvertiserView(advertiserView, null);
// Set CTA button
Button ctaView = findViewById(R.id.ad_call_to_action);
ctaView.setText(ad.getCallToAction());
nativeAdView.setCallToActionView(ctaView);
// Bind ad data
nativeAdView.setNativeAd(ad);
}
@Override
protected void onDestroy() {
// Release resources
if (nativeAdView != null) {
nativeAdView.destroy();
}
if (nativeAd != null) {
nativeAd.destroy();
}
super.onDestroy();
}
}
```
***
## Advanced Features
### Entire Click Area Setting
You can set the entire ad view area to trigger click events.
```kotlin Kotlin theme={null}
nativeAdView.isEntireClick = true
```
```java Java theme={null}
nativeAdView.setEntireClick(true);
```
When `isEntireClick` is set to `true`, the entire ad view becomes a clickable area. Individual element click listeners will not work.
### Individual Element Click Listeners
You can set custom click listeners on specific ad elements.
```kotlin Kotlin theme={null}
// Custom action on advertiser click
nativeAdView.setAdvertiserView(advertiserView) {
Log.d("Adrop", "Advertiser clicked")
// Implement custom action
}
// Custom action on profile logo click
nativeAdView.setProfileLogoView(profileLogoView) {
Log.d("Adrop", "Profile logo clicked")
// Implement custom action
}
```
```java Java theme={null}
// Custom action on advertiser click
nativeAdView.setAdvertiserView(advertiserView, new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d("Adrop", "Advertiser clicked");
// Implement custom action
}
});
// Custom action on profile logo click
nativeAdView.setProfileLogoView(profileLogoView, new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d("Adrop", "Profile logo clicked");
// Implement custom action
}
});
```
### Contextual Targeting
You can request ads matching a specific context.
```kotlin Kotlin theme={null}
val nativeAd = AdropNativeAd(
context = this,
unitId = "YOUR_UNIT_ID",
contextId = "SPORTS_NEWS" // Context ID
)
```
```java Java theme={null}
AdropNativeAd nativeAd = new AdropNativeAd(
this,
"YOUR_UNIT_ID",
"SPORTS_NEWS" // Context ID
);
```
### Using in RecyclerView
You can display native ads as items in a RecyclerView.
```kotlin RecyclerView Adapter theme={null}
class ContentAdapter : RecyclerView.Adapter() {
companion object {
const val VIEW_TYPE_CONTENT = 0
const val VIEW_TYPE_AD = 1
const val AD_INTERVAL = 5 // Show ad every 5 items
}
private val items = mutableListOf()
private val nativeAds = mutableMapOf()
override fun getItemViewType(position: Int): Int {
return if (position % (AD_INTERVAL + 1) == AD_INTERVAL) {
VIEW_TYPE_AD
} else {
VIEW_TYPE_CONTENT
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
VIEW_TYPE_AD -> {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_native_ad, parent, false)
NativeAdViewHolder(view)
}
else -> {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_content, parent, false)
ContentViewHolder(view)
}
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is NativeAdViewHolder -> loadNativeAd(holder, position)
is ContentViewHolder -> holder.bind(items[position])
}
}
private fun loadNativeAd(holder: NativeAdViewHolder, position: Int) {
// Reuse already loaded ad
nativeAds[position]?.let { ad ->
if (ad.isLoaded) {
holder.bind(ad)
return
}
}
// Load new ad
val nativeAd = AdropNativeAd(holder.itemView.context, "YOUR_UNIT_ID")
nativeAd.listener = object : AdropNativeAdListener {
override fun onAdReceived(ad: AdropNativeAd) {
nativeAds[position] = ad
holder.bind(ad)
}
override fun onAdFailedToReceive(ad: AdropNativeAd, errorCode: AdropErrorCode) {
Log.e("Adrop", "Ad load failed: $errorCode")
}
override fun onAdClicked(ad: AdropNativeAd) {
Log.d("Adrop", "Ad clicked")
}
}
nativeAd.load()
}
class NativeAdViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val nativeAdView: AdropNativeAdView = itemView.findViewById(R.id.native_ad_view)
fun bind(ad: AdropNativeAd) {
val profileLogo = itemView.findViewById(R.id.ad_profile_logo)
Glide.with(itemView.context).load(ad.profile.displayLogo).into(profileLogo)
nativeAdView.setProfileLogoView(profileLogo)
val profileName = itemView.findViewById(R.id.ad_profile_name)
profileName.text = ad.profile.displayName
nativeAdView.setProfileNameView(profileName)
val headline = itemView.findViewById(R.id.ad_headline)
headline.text = ad.headline
nativeAdView.setHeadLineView(headline)
val media = itemView.findViewById(R.id.ad_media)
nativeAdView.setMediaView(media)
val body = itemView.findViewById(R.id.ad_body)
body.text = ad.body
nativeAdView.setBodyView(body)
val advertiser = itemView.findViewById(R.id.ad_advertiser)
advertiser.text = ad.profile.displayName
nativeAdView.setAdvertiserView(advertiser)
val cta = itemView.findViewById(R.id.ad_call_to_action)
cta.text = ad.callToAction
nativeAdView.setCallToActionView(cta)
nativeAdView.setNativeAd(ad)
}
}
fun destroy() {
nativeAds.values.forEach { it.destroy() }
nativeAds.clear()
}
}
```
***
## Test Unit IDs
Use the following test unit IDs during development and testing.
| Format | Test Unit ID |
| - | - |
| Native (Image) | `PUBLIC_TEST_UNIT_ID_NATIVE` |
| Native Video (16:9) | `PUBLIC_TEST_UNIT_ID_NATIVE_VIDEO_16_9` |
| Native Video (9:16) | `PUBLIC_TEST_UNIT_ID_NATIVE_VIDEO_9_16` |
Be sure to replace with actual unit IDs before production deployment.
***
## Lifecycle Management
### Resource Release in Activity/Fragment
Native ads must be released when no longer in use to prevent memory leaks.
```kotlin Activity theme={null}
override fun onDestroy() {
nativeAdView.destroy()
nativeAd.destroy()
super.onDestroy()
}
```
```kotlin Fragment theme={null}
override fun onDestroyView() {
nativeAdView?.destroy()
nativeAd?.destroy()
super.onDestroyView()
}
```
```java Java theme={null}
@Override
protected void onDestroy() {
if (nativeAdView != null) {
nativeAdView.destroy();
}
if (nativeAd != null) {
nativeAd.destroy();
}
super.onDestroy();
}
```
### Resource Release in RecyclerView
When using ads in a RecyclerView Adapter, all ads must be released when the Activity or Fragment is destroyed.
```kotlin theme={null}
override fun onDestroy() {
adapter.destroy()
super.onDestroy()
}
```
***
## Video Tracking and VTR
Adrop measures video metrics for native creatives — including impression-to-completion (VTR), `onAdVideoStart`, and `onAdVideoEnd` — only when the video is rendered through the SDK's media surface (`AdropMediaView` bound via `AdropNativeAdView.setMediaView(...)`).
The `AdropNativeAd.asset` property returns a URL pointing to the underlying image or video file. It is exposed as supplementary metadata (for example, for thumbnails or your own analytics); it is **not** intended to drive playback.
Do not feed the `asset` URL into a custom video player (`ExoPlayer`, `MediaPlayer`, or any third-party player) for a video native ad. When the SDK's media surface is bypassed:
* **VTR (Video Through Rate) is not collected** for that placement.
* `onAdVideoStart` / `onAdVideoEnd` callbacks **never fire**.
* Aggregate video performance for the unit will under-report or report zero.
Click (`onAdClicked`) and impression (`onAdImpression`) tracking still work because they are wired to the container view, but the video-specific signals are lost.
### Recommended pattern
Bind the SDK media surface for every video native:
```kotlin Kotlin theme={null}
val mediaView = findViewById(R.id.ad_media)
nativeAdView.setMediaView(mediaView) // Required for video tracking
nativeAdView.setNativeAd(ad)
```
```java Java theme={null}
AdropMediaView mediaView = findViewById(R.id.ad_media);
nativeAdView.setMediaView(mediaView); // Required for video tracking
nativeAdView.setNativeAd(ad);
```
If a placement absolutely requires a custom player and you accept that VTR will not be measured, you can still surface click and impression attribution by keeping the ad bound to `AdropNativeAdView`. In that case, treat the placement as **non-VTR inventory** in your internal reporting and avoid mixing it with SDK-measured video performance.
***
## Best Practices
### 1. Display Only Required Elements
You don't need to display all ad elements. Selectively use only the elements needed to match your app's design.
```kotlin theme={null}
// Minimal configuration example (media + CTA only)
nativeAdView.setMediaView(mediaView)
nativeAdView.setCallToActionView(ctaView)
nativeAdView.setNativeAd(ad)
```
### 2. Use Image Loading Libraries
Use image loading libraries like Glide or Coil when displaying icon, cover, and profile images.
```kotlin theme={null}
// Glide example
Glide.with(context)
.load(ad.icon)
.placeholder(R.drawable.placeholder)
.error(R.drawable.error)
.into(iconView)
```
### 3. Reuse Ad Views
When displaying ads in a RecyclerView, cache and reuse ad instances.
### 4. Appropriate Ad Placement
Place ads naturally without degrading user experience.
* Content feed: 1 ad every 5-10 items
* Detail screen: Place at bottom of content
* Scroll view: Place to match natural content flow
### 5. Error Handling
Implement appropriate fallback handling for ad load failures.
```kotlin theme={null}
override fun onAdFailedToReceive(ad: AdropNativeAd, errorCode: AdropErrorCode) {
when (errorCode) {
AdropErrorCode.ERROR_CODE_AD_NO_FILL -> {
// No ad available - hide ad area
nativeAdView.visibility = View.GONE
}
AdropErrorCode.ERROR_CODE_NETWORK -> {
// Network error - retry logic
Handler(Looper.getMainLooper()).postDelayed({
nativeAd.load()
}, 3000)
}
else -> {
Log.e("Adrop", "Ad load failed: $errorCode")
}
}
}
```
***
## Complete Example
```kotlin Activity theme={null}
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import com.bumptech.glide.Glide
import io.adrop.ads.model.AdropErrorCode
import io.adrop.ads.nativeAd.AdropMediaView
import io.adrop.ads.nativeAd.AdropNativeAd
import io.adrop.ads.nativeAd.AdropNativeAdListener
import io.adrop.ads.nativeAd.AdropNativeAdView
class NativeAdExampleActivity : AppCompatActivity() {
private lateinit var nativeAd: AdropNativeAd
private lateinit var nativeAdView: AdropNativeAdView
private lateinit var loadingView: View
private lateinit var errorView: View
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_native_ad_example)
nativeAdView = findViewById(R.id.native_ad_view)
loadingView = findViewById(R.id.loading_view)
errorView = findViewById(R.id.error_view)
findViewById(R.id.retry_button).setOnClickListener {
loadNativeAd()
}
loadNativeAd()
}
private fun loadNativeAd() {
showLoading()
nativeAd = AdropNativeAd(
context = this,
unitId = "PUBLIC_TEST_UNIT_ID_NATIVE"
)
nativeAd.listener = object : AdropNativeAdListener {
override fun onAdReceived(ad: AdropNativeAd) {
Log.d("Adrop", "Ad received: unitId=${ad.unitId}, txId=${ad.txId}")
populateNativeAdView(ad)
showAd()
}
override fun onAdFailedToReceive(ad: AdropNativeAd, errorCode: AdropErrorCode) {
Log.e("Adrop", "Ad failed to receive: $errorCode")
showError()
}
override fun onAdClicked(ad: AdropNativeAd) {
Log.d("Adrop", "Ad clicked: txId=${ad.txId}")
}
override fun onAdImpression(ad: AdropNativeAd) {
Log.d("Adrop", "Ad impression: txId=${ad.txId}")
}
}
nativeAd.load()
}
private fun populateNativeAdView(ad: AdropNativeAd) {
// Enable entire click area (optional)
// nativeAdView.isEntireClick = true
// Profile logo
val profileLogoView = findViewById(R.id.ad_profile_logo)
Glide.with(this)
.load(ad.profile.displayLogo)
.circleCrop()
.into(profileLogoView)
nativeAdView.setProfileLogoView(profileLogoView)
// Profile name
val profileNameView = findViewById(R.id.ad_profile_name)
profileNameView.text = ad.profile.displayName
nativeAdView.setProfileNameView(profileNameView)
// Headline
val headlineView = findViewById(R.id.ad_headline)
headlineView.text = ad.headline
nativeAdView.setHeadLineView(headlineView)
// Media (image or video)
val mediaView = findViewById(R.id.ad_media)
nativeAdView.setMediaView(mediaView)
// Body
val bodyView = findViewById(R.id.ad_body)
bodyView.text = ad.body
nativeAdView.setBodyView(bodyView)
// Advertiser
val advertiserView = findViewById(R.id.ad_advertiser)
advertiserView.text = ad.profile.displayName
nativeAdView.setAdvertiserView(advertiserView) {
Log.d("Adrop", "Advertiser clicked: ${ad.profile.displayName}")
}
// CTA button
val ctaView = findViewById(R.id.ad_call_to_action)
ctaView.text = ad.callToAction
nativeAdView.setCallToActionView(ctaView)
// Bind ad data
nativeAdView.setNativeAd(ad)
}
private fun showLoading() {
loadingView.visibility = View.VISIBLE
nativeAdView.visibility = View.GONE
errorView.visibility = View.GONE
}
private fun showAd() {
loadingView.visibility = View.GONE
nativeAdView.visibility = View.VISIBLE
errorView.visibility = View.GONE
}
private fun showError() {
loadingView.visibility = View.GONE
nativeAdView.visibility = View.GONE
errorView.visibility = View.VISIBLE
}
override fun onDestroy() {
nativeAdView.destroy()
nativeAd.destroy()
super.onDestroy()
}
}
```
```java Java theme={null}
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import com.bumptech.glide.Glide;
import io.adrop.ads.model.AdropErrorCode;
import io.adrop.ads.nativeAd.AdropMediaView;
import io.adrop.ads.nativeAd.AdropNativeAd;
import io.adrop.ads.nativeAd.AdropNativeAdListener;
import io.adrop.ads.nativeAd.AdropNativeAdView;
public class NativeAdExampleActivity extends AppCompatActivity {
private AdropNativeAd nativeAd;
private AdropNativeAdView nativeAdView;
private View loadingView;
private View errorView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_native_ad_example);
nativeAdView = findViewById(R.id.native_ad_view);
loadingView = findViewById(R.id.loading_view);
errorView = findViewById(R.id.error_view);
findViewById(R.id.retry_button).setOnClickListener(v -> loadNativeAd());
loadNativeAd();
}
private void loadNativeAd() {
showLoading();
nativeAd = new AdropNativeAd(
this,
"PUBLIC_TEST_UNIT_ID_NATIVE",
null
);
nativeAd.setListener(new AdropNativeAdListener() {
@Override
public void onAdReceived(AdropNativeAd ad) {
Log.d("Adrop", "Ad received: unitId=" + ad.getUnitId() + ", txId=" + ad.getTxId());
populateNativeAdView(ad);
showAd();
}
@Override
public void onAdFailedToReceive(AdropNativeAd ad, AdropErrorCode errorCode) {
Log.e("Adrop", "Ad failed to receive: " + errorCode);
showError();
}
@Override
public void onAdClicked(AdropNativeAd ad) {
Log.d("Adrop", "Ad clicked: txId=" + ad.getTxId());
}
@Override
public void onAdImpression(AdropNativeAd ad) {
Log.d("Adrop", "Ad impression: txId=" + ad.getTxId());
}
});
nativeAd.load();
}
private void populateNativeAdView(AdropNativeAd ad) {
// Profile logo
ImageView profileLogoView = findViewById(R.id.ad_profile_logo);
Glide.with(this)
.load(ad.getProfile().getDisplayLogo())
.circleCrop()
.into(profileLogoView);
nativeAdView.setProfileLogoView(profileLogoView, null);
// Profile name
TextView profileNameView = findViewById(R.id.ad_profile_name);
profileNameView.setText(ad.getProfile().getDisplayName());
nativeAdView.setProfileNameView(profileNameView, null);
// Headline
TextView headlineView = findViewById(R.id.ad_headline);
headlineView.setText(ad.getHeadline());
nativeAdView.setHeadLineView(headlineView, null);
// Media
AdropMediaView mediaView = findViewById(R.id.ad_media);
nativeAdView.setMediaView(mediaView);
// Body
TextView bodyView = findViewById(R.id.ad_body);
bodyView.setText(ad.getBody());
nativeAdView.setBodyView(bodyView);
// Advertiser
TextView advertiserView = findViewById(R.id.ad_advertiser);
advertiserView.setText(ad.getProfile().getDisplayName());
nativeAdView.setAdvertiserView(advertiserView, v -> {
Log.d("Adrop", "Advertiser clicked: " + ad.getProfile().getDisplayName());
});
// CTA button
Button ctaView = findViewById(R.id.ad_call_to_action);
ctaView.setText(ad.getCallToAction());
nativeAdView.setCallToActionView(ctaView);
// Bind ad data
nativeAdView.setNativeAd(ad);
}
private void showLoading() {
loadingView.setVisibility(View.VISIBLE);
nativeAdView.setVisibility(View.GONE);
errorView.setVisibility(View.GONE);
}
private void showAd() {
loadingView.setVisibility(View.GONE);
nativeAdView.setVisibility(View.VISIBLE);
errorView.setVisibility(View.GONE);
}
private void showError() {
loadingView.setVisibility(View.GONE);
nativeAdView.setVisibility(View.GONE);
errorView.setVisibility(View.VISIBLE);
}
@Override
protected void onDestroy() {
if (nativeAdView != null) {
nativeAdView.destroy();
}
if (nativeAd != null) {
nativeAd.destroy();
}
super.onDestroy();
}
}
```
***
## Batch Loading (loads)
Use `AdropNativeAd.loads()` to request multiple native ads in a single batched call. This is useful for prefetching a pool of ads to insert into feeds, carousels, or paginated lists.
### Signature
```kotlin Kotlin theme={null}
AdropNativeAd.loads(
context: Context,
unitId: String,
contextId: String? = null,
listener: AdropNativeAdListener,
)
```
```java Java theme={null}
AdropNativeAd.loads(context, "YOUR_UNIT_ID", null, listener);
```
### Usage
```kotlin Kotlin theme={null}
AdropNativeAd.loads(
context = this,
unitId = "YOUR_UNIT_ID",
contextId = null,
listener = object : AdropNativeAdListener {
// Batch success — each ad is fully rendered and ready to bind.
override fun onAdsReceived(ads: List) {
ads.forEach { ad ->
// Bind to an AdropNativeAdView or store for later lifecycle.
bindToView(ad)
}
}
// Batch failure
override fun onAdsFailedToReceive(errorCode: AdropErrorCode) {
Log.e("Adrop", "Batch load failed: $errorCode")
}
// Single-load callbacks are NOT invoked on this path.
override fun onAdReceived(ad: AdropNativeAd) { /* batch path */ }
override fun onAdFailedToReceive(ad: AdropNativeAd, errorCode: AdropErrorCode) {}
// Click / impression / video callbacks still fire per instance.
override fun onAdClicked(ad: AdropNativeAd) {
Log.d("Adrop", "Native clicked: ${ad.creativeId}")
}
override fun onAdImpression(ad: AdropNativeAd) {
Log.d("Adrop", "Native impression: ${ad.creativeId}")
}
},
)
```
```java Java theme={null}
AdropNativeAd.loads(this, "YOUR_UNIT_ID", null, new AdropNativeAdListener() {
@Override
public void onAdsReceived(@NotNull List ads) {
for (AdropNativeAd ad : ads) {
bindToView(ad);
}
}
@Override
public void onAdsFailedToReceive(@NotNull AdropErrorCode errorCode) {
Log.e("Adrop", "Batch load failed: " + errorCode);
}
@Override
public void onAdReceived(@NotNull AdropNativeAd ad) { }
@Override
public void onAdFailedToReceive(@NotNull AdropNativeAd ad, @NotNull AdropErrorCode errorCode) { }
@Override
public void onAdClicked(@NotNull AdropNativeAd ad) {
Log.d("Adrop", "Native clicked: " + ad.getCreativeId());
}
@Override
public void onAdImpression(@NotNull AdropNativeAd ad) {
Log.d("Adrop", "Native impression: " + ad.getCreativeId());
}
});
```
### Listener Callbacks
| Callback | When it fires |
| - | - |
| `onAdsReceived(ads)` | Batch delivered. **Every ad is fully rendered** — creative is ready to bind. |
| `onAdsFailedToReceive(errorCode)` | Request rejected, network failure, or no fillable ads were returned (`ERROR_CODE_AD_NO_FILL`). |
| `onAdClicked` / `onAdImpression` / `onAdVideoStart` / `onAdVideoEnd` | Fire on each returned ad just like a single `load()`. |
The singular `onAdReceived` / `onAdFailedToReceive` callbacks are **not** invoked on the `loads()` path. Use `onAdsReceived` / `onAdsFailedToReceive` as the sole batch signals.
### Constraints
* **Maximum 5 ads per call.** If the server returns more, only the first 5 are delivered.
* **Backfill is not applied.** If no direct ads fill, `onAdsFailedToReceive` is called with `ERROR_CODE_AD_NO_FILL` regardless of backfill configuration.
* **Do not call `destroy()` inside `onAdsReceived`.** Bind each ad to an `AdropNativeAdView` via `setNativeAd(...)` or store for later lifecycle, then `destroy()` on the parent `Activity.onDestroy()` (or equivalent).
* Unlike `AdropBanner.loads()`, the native batch waits for every returned ad to finish rendering before firing `onAdsReceived`, so each instance is fully rendered on delivery.
***
## Backfill Ads
When backfill ads are enabled, backfill ads are automatically loaded when direct ads are unavailable. Use the `isBackfilled` property to check if the ad is a backfill ad.
```kotlin Kotlin theme={null}
nativeAd.listener = object : AdropNativeAdListener {
override fun onAdReceived(ad: AdropNativeAd) {
if (ad.isBackfilled) {
println("Backfill ad loaded")
} else {
println("Direct ad loaded")
}
}
override fun onAdFailedToReceive(ad: AdropNativeAd, errorCode: AdropErrorCode) {
when (errorCode) {
AdropErrorCode.ERROR_CODE_AD_NO_FILL -> {
println("No direct ad, requesting backfill...")
}
AdropErrorCode.ERROR_CODE_AD_BACKFILL_NO_FILL -> {
println("Backfill ad also not available")
}
else -> {
println("Ad load failed: $errorCode")
}
}
}
}
```
```java Java theme={null}
nativeAd.setListener(new AdropNativeAdListener() {
@Override
public void onAdReceived(AdropNativeAd ad) {
if (ad.isBackfilled()) {
System.out.println("Backfill ad loaded");
} else {
System.out.println("Direct ad loaded");
}
}
@Override
public void onAdFailedToReceive(AdropNativeAd ad, AdropErrorCode errorCode) {
if (errorCode == AdropErrorCode.ERROR_CODE_AD_NO_FILL) {
System.out.println("No direct ad, requesting backfill...");
} else if (errorCode == AdropErrorCode.ERROR_CODE_AD_BACKFILL_NO_FILL) {
System.out.println("Backfill ad also not available");
} else {
System.out.println("Ad load failed: " + errorCode);
}
}
});
```
### AdChoices Position
For backfill native ads, you can choose which corner displays the AdChoices badge. Set `preferredAdChoicesPosition` on the `AdropNativeAd` instance before calling `load()`.
```kotlin Kotlin theme={null}
import io.adrop.ads.nativeAd.AdropAdChoicesPosition
import io.adrop.ads.nativeAd.AdropNativeAd
val nativeAd = AdropNativeAd(this, "YOUR_UNIT_ID")
nativeAd.preferredAdChoicesPosition = AdropAdChoicesPosition.BOTTOM_RIGHT
nativeAd.load()
```
```java Java theme={null}
import io.adrop.ads.nativeAd.AdropAdChoicesPosition;
import io.adrop.ads.nativeAd.AdropNativeAd;
AdropNativeAd nativeAd = new AdropNativeAd(this, "YOUR_UNIT_ID");
nativeAd.setPreferredAdChoicesPosition(AdropAdChoicesPosition.BOTTOM_RIGHT);
nativeAd.load();
```
| Value | Description |
| - | - |
| `AdropAdChoicesPosition.TOP_LEFT` | Top-left corner |
| `AdropAdChoicesPosition.TOP_RIGHT` | Top-right corner (default) |
| `AdropAdChoicesPosition.BOTTOM_LEFT` | Bottom-left corner |
| `AdropAdChoicesPosition.BOTTOM_RIGHT` | Bottom-right corner |
This setting is a preferred hint for the backfill network and applies only to backfill native ads — direct ads are not affected. The value must be set before `load()` is called. The backfill network may override the position depending on its policy.
To use backfill ads, add the `io.adrop:adrop-ads-backfill` dependency. See [Getting Started](/sdk/android/overview).
***
## Next Steps
Implement banner ads
Implement interstitial ads
User properties and context targeting
Classes, listeners, error codes
# Getting Started
Source: https://docs.adrop.io/sdk/android/overview
Learn how to integrate the Adrop SDK into your Android project.
## Overview
Use the Adrop Android SDK to display various ad formats in your app.
### Supported Ad Formats
| Format | Description |
| - | - |
| **Banner** | Rectangular ads displayed in a portion of the screen |
| **Native** | Customizable ads that match your app's UI |
| **Interstitial** | Full-screen ads |
| **Rewarded** | Video ads that reward users upon completion |
| **Popup** | Ads displayed as popups at specific moments |
| **Splash** | Ads displayed with your logo at app launch |
### Requirements
* Android 6.0 (API Level 23) or higher
* Gradle 8.7 or higher
* Kotlin 2.1.0 or higher
* compileSdkVersion 34 or higher
* Jetpack (AndroidX) support
***
## Prerequisites
### 1. Add adrop\_service.json File
Download the `adrop_service.json` file from [Ad Control Console](https://console.adrop.io) > **Admin** > **App**.
Place the downloaded file in the `app/src/main/assets/` folder.
```
app/
└── src/
└── main/
└── assets/
└── adrop_service.json
```
The SDK will not work properly without the `adrop_service.json` file.
### 2. Get Unit ID
Find the unit ID for your ad placement in the **Ad Unit** tab of the console.
***
## Installation
### Gradle Setup
#### Settings.gradle.kts
Add the repository to your project-level `settings.gradle.kts` file:
```kotlin settings.gradle.kts theme={null}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven { url = uri("https://artifact.bytedance.com/repository/pangle") }
}
}
```
```groovy settings.gradle theme={null}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven { url = uri("https://artifact.bytedance.com/repository/pangle") }
}
}
```
#### Build.gradle.kts
Add the dependency to your app-level `build.gradle` or `build.gradle.kts` file:
```kotlin build.gradle.kts theme={null}
dependencies {
implementation("io.adrop:adrop-ads:1.15.0")
implementation("io.adrop:adrop-ads-backfill:1.15.0")
}
```
```groovy build.gradle theme={null}
dependencies {
implementation "io.adrop:adrop-ads:1.15.0"
implementation "io.adrop:adrop-ads-backfill:1.15.0"
}
```
Check the latest version on [Maven Central](https://central.sonatype.com/artifact/io.adrop/adrop-ads).
### AndroidManifest.xml Setup
Add the App ID to your `AndroidManifest.xml` for backfill ads:
```xml AndroidManifest.xml theme={null}
```
Please contact Adrop to get your App ID.
***
## Initialization
Initialize the SDK in the `onCreate()` method of your `Application` class.
```kotlin Kotlin theme={null}
import android.app.Application
import io.adrop.ads.Adrop
import io.adrop.ads.model.AdropTheme
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// Initialize SDK
Adrop.initialize(this, production = false, targetCountries = arrayOf())
Adrop.setTheme(AdropTheme.AUTO)
}
}
```
```java Java theme={null}
import android.app.Application;
import io.adrop.ads.Adrop;
import io.adrop.ads.model.AdropTheme;
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// Initialize SDK
Adrop.INSTANCE.initialize(this, false, new String[]{});
Adrop.INSTANCE.setTheme(AdropTheme.AUTO);
}
}
```
### Initialization Parameters
| Parameter | Type | Default | Description |
| - | - | - | - |
| `context` | `Application` | - | App's Application instance (required) |
| `production` | `Boolean` | `false` | Production mode. Set to `true` for release |
| `targetCountries` | `Array` | `[]` | Target country codes (ISO 3166 alpha-2) |
| `tokenKey` | `String?` | `null` | App token key from `adrop_service.json` (optional) |
If your `adrop_service.json` contains multiple app configurations, use `tokenKey` to specify which key to use for initialization.
Make sure to set `production = true` before release. Ads will not be displayed if set to `false` in production.
***
## User Settings
### Set UID
Set a user identifier for targeted advertising.
```kotlin Kotlin theme={null}
Adrop.setUID("user_123")
```
```java Java theme={null}
Adrop.INSTANCE.setUID("user_123");
```
Set the UID before entering the ad placement for targeted ads to work properly.
### Set Theme
Set the theme for ads that support dark mode. Must be called after `initialize()`.
```kotlin Kotlin theme={null}
// Light mode
Adrop.setTheme(AdropTheme.LIGHT)
// Dark mode
Adrop.setTheme(AdropTheme.DARK)
// Follow system setting (recommended)
Adrop.setTheme(AdropTheme.AUTO)
```
```java Java theme={null}
// Light mode
Adrop.INSTANCE.setTheme(AdropTheme.LIGHT);
// Dark mode
Adrop.INSTANCE.setTheme(AdropTheme.DARK);
// Follow system setting (recommended)
Adrop.INSTANCE.setTheme(AdropTheme.AUTO);
```
`AUTO` mode automatically detects the system dark mode setting. When the theme changes, the splash ad cache is automatically cleared.
***
## Deep Link Handling
Handle deep links when the app is launched via an external link.
```kotlin Kotlin theme={null}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
intent?.let { Adrop.handleDeepLink(it) }
}
```
```java Java theme={null}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (intent != null) {
Adrop.INSTANCE.handleDeepLink(intent);
}
}
```
***
## Test Unit IDs
Use test unit IDs during development. Replace with real unit IDs before production release.
### Banner Ads
| Format | Test Unit ID |
| - | - |
| Banner (320x50) | `PUBLIC_TEST_UNIT_ID_320_50` |
| Banner (320x100) | `PUBLIC_TEST_UNIT_ID_320_100` |
| Carousel Banner | `PUBLIC_TEST_UNIT_ID_CAROUSEL` |
| Banner Video (16:9) | `PUBLIC_TEST_UNIT_ID_BANNER_VIDEO_16_9` |
| Banner Video (9:16) | `PUBLIC_TEST_UNIT_ID_BANNER_VIDEO_9_16` |
### Native Ads
| Format | Test Unit ID |
| - | - |
| Native (Image) | `PUBLIC_TEST_UNIT_ID_NATIVE` |
| Native Video (16:9) | `PUBLIC_TEST_UNIT_ID_NATIVE_VIDEO_16_9` |
| Native Video (9:16) | `PUBLIC_TEST_UNIT_ID_NATIVE_VIDEO_9_16` |
### Interstitial/Rewarded Ads
| Format | Test Unit ID |
| - | - |
| Interstitial | `PUBLIC_TEST_UNIT_ID_INTERSTITIAL` |
| Rewarded | `PUBLIC_TEST_UNIT_ID_REWARDED` |
### Popup Ads
| Format | Test Unit ID |
| - | - |
| Popup (Bottom) | `PUBLIC_TEST_UNIT_ID_POPUP_BOTTOM` |
| Popup (Center) | `PUBLIC_TEST_UNIT_ID_POPUP_CENTER` |
| Popup Video Bottom (16:9) | `PUBLIC_TEST_UNIT_ID_POPUP_BOTTOM_VIDEO_16_9` |
| Popup Video Bottom (9:16) | `PUBLIC_TEST_UNIT_ID_POPUP_BOTTOM_VIDEO_9_16` |
| Popup Video Center (16:9) | `PUBLIC_TEST_UNIT_ID_POPUP_CENTER_VIDEO_16_9` |
| Popup Video Center (9:16) | `PUBLIC_TEST_UNIT_ID_POPUP_CENTER_VIDEO_9_16` |
### Splash Ads
| Format | Test Unit ID |
| - | - |
| Splash | `PUBLIC_TEST_UNIT_ID_SPLASH` |
***
## Error Codes
Error codes returned when ad loading or display fails.
| Error Code | Description |
| - | - |
| `ERROR_CODE_NETWORK` | Network error |
| `ERROR_CODE_INTERNAL` | Internal error |
| `ERROR_CODE_INITIALIZE` | SDK initialization error |
| `ERROR_CODE_INVALID_UNIT` | Invalid ad unit ID |
| `ERROR_CODE_NOT_TARGET_COUNTRY` | Not in target country |
| `ERROR_CODE_AD_INACTIVE` | Inactive ad |
| `ERROR_CODE_AD_NO_FILL` | No ad available to display |
| `ERROR_CODE_AD_LOAD_DUPLICATED` | Duplicate ad load request |
| `ERROR_CODE_AD_LOADING` | Ad is loading |
| `ERROR_CODE_AD_EMPTY` | Ad is empty |
| `ERROR_CODE_AD_SHOWN` | Ad has already been shown |
| `ERROR_CODE_AD_HIDE_FOR_TODAY` | "Don't show today" is set |
| `ERROR_CODE_ACCOUNT_USAGE_LIMIT_EXCEEDED` | Account usage limit exceeded |
| `ERROR_CODE_LANDSCAPE_UNSUPPORTED` | Landscape mode not supported |
| `ERROR_CODE_AD_BACKFILL_NO_FILL` | No backfill ad available |
For detailed descriptions, see the [Reference](/sdk/android/reference#adroperrorcode).
***
## Table of Contents
Implement banner ads
Implement native ads
Implement interstitial ads
Implement rewarded ads
Implement popup ads
Implement splash ads
Display web ads in WebView
User properties and context targeting
Classes, listeners, and error codes
Example repository
# Popup Ads
Source: https://docs.adrop.io/sdk/android/popup
Learn how to implement popup ads in your Android app.
## Overview
Popup Ads are an ad format that appears on screen at specific moments. They can be displayed at desired timings such as app launch, content load completion, or specific event triggers. Users can close them by tapping the close button or selecting "Don't show today."
### Features
* Popup form displayed at center or bottom of screen
* Image and video ad support
* Close, dim (background) click, and "Don't show today" options
* Customizable background color and text colors
* Non-intrusive yet effective ad experience
Use test unit IDs in development. See the [Test Unit IDs](#test-unit-ids) section.
***
## Implementation Steps
Implement popup ads in 4 steps:
1. **Initialize** - Create AdropPopupAd instance
2. **Set Listeners** - Set listeners to receive ad and close events
3. **Load Ad** - Request and receive ad
4. **Show Ad** - Display ad on screen
***
## Basic Implementation
### Basic Usage
```kotlin Kotlin theme={null}
import io.adrop.ads.popupAd.AdropPopupAd
import io.adrop.ads.popupAd.AdropPopupAdListener
import io.adrop.ads.popupAd.AdropPopupAdCloseListener
import io.adrop.ads.model.AdropErrorCode
class MainActivity : AppCompatActivity() {
private var popupAd: AdropPopupAd? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Load popup ad
loadPopupAd()
}
// 1. Initialize and load ad
private fun loadPopupAd() {
popupAd = AdropPopupAd(this, "YOUR_POPUP_UNIT_ID")
popupAd?.popupAdListener = popupAdListener
popupAd?.closeListener = popupCloseListener
popupAd?.load()
}
// 2. Show ad
private fun showPopupAd() {
popupAd?.let { ad ->
if (ad.isLoaded) {
ad.show(this)
}
}
}
override fun onDestroy() {
super.onDestroy()
popupAd?.destroy()
}
}
```
```java Java theme={null}
import io.adrop.ads.popupAd.AdropPopupAd;
import io.adrop.ads.popupAd.AdropPopupAdListener;
import io.adrop.ads.popupAd.AdropPopupAdCloseListener;
import io.adrop.ads.model.AdropErrorCode;
public class MainActivity extends AppCompatActivity {
private AdropPopupAd popupAd;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Load popup ad
loadPopupAd();
}
// 1. Initialize and load ad
private void loadPopupAd() {
popupAd = new AdropPopupAd(this, "YOUR_POPUP_UNIT_ID");
popupAd.setPopupAdListener(popupAdListener);
popupAd.setCloseListener(popupCloseListener);
popupAd.load();
}
// 2. Show ad
private void showPopupAd() {
if (popupAd != null && popupAd.isLoaded()) {
popupAd.show(this);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (popupAd != null) {
popupAd.destroy();
}
}
}
```
### Implementing Listeners
```kotlin Kotlin theme={null}
// Ad listener implementation
private val popupAdListener = object : AdropPopupAdListener {
// Ad received successfully (Required)
override fun onAdReceived(ad: AdropPopupAd) {
Log.d("Adrop", "Popup ad received")
// Show ad when ready
showPopupAd()
}
// Ad failed to receive (Required)
override fun onAdFailedToReceive(ad: AdropPopupAd, errorCode: AdropErrorCode) {
Log.d("Adrop", "Popup ad failed to receive: $errorCode")
}
// Ad impression (Optional)
override fun onAdImpression(ad: AdropPopupAd) {
Log.d("Adrop", "Popup ad impression")
}
// Ad clicked (Optional)
override fun onAdClicked(ad: AdropPopupAd) {
Log.d("Adrop", "Popup ad clicked")
}
// Just before popup ad is displayed (Optional)
override fun onAdWillPresentFullScreen(ad: AdropPopupAd) {
Log.d("Adrop", "Popup ad will present")
}
// Just after popup ad is displayed (Optional)
override fun onAdDidPresentFullScreen(ad: AdropPopupAd) {
Log.d("Adrop", "Popup ad presented")
}
// Just before popup ad is closed (Optional)
override fun onAdWillDismissFullScreen(ad: AdropPopupAd) {
Log.d("Adrop", "Popup ad will dismiss")
}
// Just after popup ad is closed (Optional)
override fun onAdDidDismissFullScreen(ad: AdropPopupAd) {
Log.d("Adrop", "Popup ad dismissed")
// Preload next ad
loadPopupAd()
}
// Popup ad failed to show (Optional)
override fun onAdFailedToShowFullScreen(ad: AdropPopupAd, errorCode: AdropErrorCode) {
Log.d("Adrop", "Popup ad failed to show: $errorCode")
}
// Video playback started (Optional)
override fun onAdVideoStart(ad: AdropPopupAd) {
Log.d("Adrop", "Popup ad video started")
}
// Video playback ended (Optional)
override fun onAdVideoEnd(ad: AdropPopupAd) {
Log.d("Adrop", "Popup ad video ended")
}
}
// Close listener implementation
private val popupCloseListener = object : AdropPopupAdCloseListener {
// Close button clicked (Optional)
override fun onClosed(ad: AdropPopupAd) {
Log.d("Adrop", "Popup ad close button clicked")
}
// Dim (background) clicked (Optional)
override fun onDimClicked(ad: AdropPopupAd) {
Log.d("Adrop", "Popup ad dim area clicked")
}
// "Don't show today" clicked (Optional)
override fun onTodayOffClicked(ad: AdropPopupAd) {
Log.d("Adrop", "Don't show today selected")
// Save today's date to control next display
val prefs = getSharedPreferences("adrop_prefs", Context.MODE_PRIVATE)
prefs.edit().putLong("last_popup_hidden_date", System.currentTimeMillis()).apply()
}
}
```
```java Java theme={null}
// Ad listener implementation
private final AdropPopupAdListener popupAdListener = new AdropPopupAdListener() {
// Ad received successfully (Required)
@Override
public void onAdReceived(@NotNull AdropPopupAd ad) {
Log.d("Adrop", "Popup ad received");
// Show ad when ready
showPopupAd();
}
// Ad failed to receive (Required)
@Override
public void onAdFailedToReceive(@NotNull AdropPopupAd ad, @NotNull AdropErrorCode errorCode) {
Log.d("Adrop", "Popup ad failed to receive: " + errorCode);
}
// Ad impression (Optional)
@Override
public void onAdImpression(@NotNull AdropPopupAd ad) {
Log.d("Adrop", "Popup ad impression");
}
// Ad clicked (Optional)
@Override
public void onAdClicked(@NotNull AdropPopupAd ad) {
Log.d("Adrop", "Popup ad clicked");
}
// Just before popup ad is displayed (Optional)
@Override
public void onAdWillPresentFullScreen(@NotNull AdropPopupAd ad) {
Log.d("Adrop", "Popup ad will present");
}
// Just after popup ad is displayed (Optional)
@Override
public void onAdDidPresentFullScreen(@NotNull AdropPopupAd ad) {
Log.d("Adrop", "Popup ad presented");
}
// Just before popup ad is closed (Optional)
@Override
public void onAdWillDismissFullScreen(@NotNull AdropPopupAd ad) {
Log.d("Adrop", "Popup ad will dismiss");
}
// Just after popup ad is closed (Optional)
@Override
public void onAdDidDismissFullScreen(@NotNull AdropPopupAd ad) {
Log.d("Adrop", "Popup ad dismissed");
// Preload next ad
loadPopupAd();
}
// Popup ad failed to show (Optional)
@Override
public void onAdFailedToShowFullScreen(@NotNull AdropPopupAd ad, @NotNull AdropErrorCode errorCode) {
Log.d("Adrop", "Popup ad failed to show: " + errorCode);
}
// Video playback started (Optional)
@Override
public void onAdVideoStart(@NotNull AdropPopupAd ad) {
Log.d("Adrop", "Popup ad video started");
}
// Video playback ended (Optional)
@Override
public void onAdVideoEnd(@NotNull AdropPopupAd ad) {
Log.d("Adrop", "Popup ad video ended");
}
};
// Close listener implementation
private final AdropPopupAdCloseListener popupCloseListener = new AdropPopupAdCloseListener() {
// Close button clicked (Optional)
@Override
public void onClosed(@NotNull AdropPopupAd ad) {
Log.d("Adrop", "Popup ad close button clicked");
}
// Dim (background) clicked (Optional)
@Override
public void onDimClicked(@NotNull AdropPopupAd ad) {
Log.d("Adrop", "Popup ad dim area clicked");
}
// "Don't show today" clicked (Optional)
@Override
public void onTodayOffClicked(@NotNull AdropPopupAd ad) {
Log.d("Adrop", "Don't show today selected");
// Save today's date to control next display
SharedPreferences prefs = getSharedPreferences("adrop_prefs", Context.MODE_PRIVATE);
prefs.edit().putLong("last_popup_hidden_date", System.currentTimeMillis()).apply();
}
};
```
***
## Customization
You can customize the appearance of popup ads.
### Setting Background and Text Colors
```kotlin Kotlin theme={null}
private fun loadPopupAd() {
popupAd = AdropPopupAd(this, "YOUR_POPUP_UNIT_ID")
popupAd?.popupAdListener = popupAdListener
popupAd?.closeListener = popupCloseListener
// Set background color (dim area)
popupAd?.backgroundColor = Color.parseColor("#CC000000")
// "Don't show today" text color
popupAd?.hideForTodayTextColor = Color.WHITE
// Close button text color
popupAd?.closeTextColor = Color.WHITE
// CTA button text color
popupAd?.ctaTextColor = Color.BLUE
popupAd?.load()
}
```
```java Java theme={null}
private void loadPopupAd() {
popupAd = new AdropPopupAd(this, "YOUR_POPUP_UNIT_ID");
popupAd.setPopupAdListener(popupAdListener);
popupAd.setCloseListener(popupCloseListener);
// Set background color (dim area)
popupAd.setBackgroundColor(Color.parseColor("#CC000000"));
// "Don't show today" text color
popupAd.setHideForTodayTextColor(Color.WHITE);
// Close button text color
popupAd.setCloseTextColor(Color.WHITE);
// CTA button text color
popupAd.setCtaTextColor(Color.BLUE);
popupAd.load();
}
```
### Customization Options
| Property | Type | Description | Default |
| - | - | - | - |
| `backgroundColor` | `Int?` | Popup background (dim) color | Black (0.8 opacity) |
| `hideForTodayTextColor` | `Int?` | "Don't show today" text color | White |
| `closeTextColor` | `Int?` | Close button text color | White |
| `ctaTextColor` | `Int?` | CTA button text color | System default |
***
## Custom Click Handling
To handle ad clicks with custom processing instead of opening the default browser, use the `useCustomClick` property.
```kotlin Kotlin theme={null}
private fun loadPopupAd() {
popupAd = AdropPopupAd(this, "YOUR_POPUP_UNIT_ID")
// Enable custom click handling
popupAd?.useCustomClick = true
popupAd?.popupAdListener = object : AdropPopupAdListener {
override fun onAdReceived(ad: AdropPopupAd) {
showPopupAd()
}
override fun onAdFailedToReceive(ad: AdropPopupAd, errorCode: AdropErrorCode) {
Log.d("Adrop", "Ad failed to receive: $errorCode")
}
override fun onAdClicked(ad: AdropPopupAd) {
// Custom click handling
Log.d("Adrop", "Ad clicked: ${ad.destinationURL}")
// Open with custom URL
popupAd?.open("https://your-custom-url.com")
// Or open with in-app browser
// openInAppBrowser(ad.destinationURL)
}
}
popupAd?.load()
}
```
```java Java theme={null}
private void loadPopupAd() {
popupAd = new AdropPopupAd(this, "YOUR_POPUP_UNIT_ID");
// Enable custom click handling
popupAd.setUseCustomClick(true);
popupAd.setPopupAdListener(new AdropPopupAdListener() {
@Override
public void onAdReceived(@NotNull AdropPopupAd ad) {
showPopupAd();
}
@Override
public void onAdFailedToReceive(@NotNull AdropPopupAd ad, @NotNull AdropErrorCode errorCode) {
Log.d("Adrop", "Ad failed to receive: " + errorCode);
}
@Override
public void onAdClicked(@NotNull AdropPopupAd ad) {
// Custom click handling
Log.d("Adrop", "Ad clicked: " + ad.getDestinationURL());
// Open with custom URL
popupAd.open("https://your-custom-url.com");
// Or open with in-app browser
// openInAppBrowser(ad.getDestinationURL());
}
});
popupAd.load();
}
```
***
## Ad Properties
The following properties are available on the popup ad object:
Ad unit ID
Whether ad is loaded
Currently displayed ad creative ID
URL to navigate to when ad is clicked
Transaction ID (for ad impression tracking)
Campaign ID
***
## Listener Methods
### AdropPopupAdListener (Ad Events)
#### Required Methods
Called when ad is received successfully. You can call `show()` at this point to display the ad.
Called when ad fails to load. The error code indicates the cause of failure.
#### Optional Methods
Called when ad impression is recorded.
Called when user clicks the ad.
Called just before popup ad is displayed.
Called just after popup ad is displayed on screen.
Called just before popup ad is closed.
Called just after popup ad is closed. This is a good time to preload the next ad.
Called when ad fails to show. The error code indicates the cause of failure.
Called when video playback starts in a video popup ad.
Called when video playback ends in a video popup ad.
### AdropPopupAdCloseListener (Close Events)
All methods are optional.
Called when user clicks the close button.
Called when user clicks outside the popup (dim area).
Called when user selects "Don't show today." You can save the date in this callback to not show ads for the day.
***
## Implementing "Don't Show Today"
Here's how to properly handle when a user selects "Don't show today."
### Implementation Using SharedPreferences
```kotlin Kotlin theme={null}
class PopupAdManager(private val context: Context) {
private val prefs = context.getSharedPreferences("adrop_prefs", Context.MODE_PRIVATE)
private val todayOffKey = "popup_ad_today_off_date"
private var popupAd: AdropPopupAd? = null
// Check if ad can be shown today
fun canShowAdToday(): Boolean {
val lastHiddenDate = prefs.getLong(todayOffKey, 0L)
if (lastHiddenDate == 0L) return true
val calendar = Calendar.getInstance()
calendar.timeInMillis = lastHiddenDate
val hiddenDay = calendar.get(Calendar.DAY_OF_YEAR)
val hiddenYear = calendar.get(Calendar.YEAR)
calendar.timeInMillis = System.currentTimeMillis()
val currentDay = calendar.get(Calendar.DAY_OF_YEAR)
val currentYear = calendar.get(Calendar.YEAR)
// Can show if saved date is not today
return hiddenDay != currentDay || hiddenYear != currentYear
}
// Load and show ad
fun loadAndShowAd(activity: Activity, unitId: String) {
if (!canShowAdToday()) {
Log.d("Adrop", "Not showing ad today")
return
}
popupAd?.destroy()
popupAd = AdropPopupAd(activity, unitId)
popupAd?.popupAdListener = popupAdListener
popupAd?.closeListener = popupCloseListener
popupAd?.load()
}
private val popupAdListener = object : AdropPopupAdListener {
override fun onAdReceived(ad: AdropPopupAd) {
// Auto-show after receiving ad
if (context is Activity) {
ad.show(context)
}
}
override fun onAdFailedToReceive(ad: AdropPopupAd, errorCode: AdropErrorCode) {
Log.d("Adrop", "Ad failed to receive: $errorCode")
}
}
private val popupCloseListener = object : AdropPopupAdCloseListener {
override fun onTodayOffClicked(ad: AdropPopupAd) {
// Save today's date
prefs.edit().putLong(todayOffKey, System.currentTimeMillis()).apply()
Log.d("Adrop", "Not showing popup ad today")
}
}
fun destroy() {
popupAd?.destroy()
popupAd = null
}
}
```
```java Java theme={null}
public class PopupAdManager {
private Context context;
private SharedPreferences prefs;
private static final String PREFS_NAME = "adrop_prefs";
private static final String TODAY_OFF_KEY = "popup_ad_today_off_date";
private AdropPopupAd popupAd;
public PopupAdManager(Context context) {
this.context = context;
this.prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
}
// Check if ad can be shown today
public boolean canShowAdToday() {
long lastHiddenDate = prefs.getLong(TODAY_OFF_KEY, 0L);
if (lastHiddenDate == 0L) return true;
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(lastHiddenDate);
int hiddenDay = calendar.get(Calendar.DAY_OF_YEAR);
int hiddenYear = calendar.get(Calendar.YEAR);
calendar.setTimeInMillis(System.currentTimeMillis());
int currentDay = calendar.get(Calendar.DAY_OF_YEAR);
int currentYear = calendar.get(Calendar.YEAR);
// Can show if saved date is not today
return hiddenDay != currentDay || hiddenYear != currentYear;
}
// Load and show ad
public void loadAndShowAd(Activity activity, String unitId) {
if (!canShowAdToday()) {
Log.d("Adrop", "Not showing ad today");
return;
}
if (popupAd != null) {
popupAd.destroy();
}
popupAd = new AdropPopupAd(activity, unitId);
popupAd.setPopupAdListener(popupAdListener);
popupAd.setCloseListener(popupCloseListener);
popupAd.load();
}
private final AdropPopupAdListener popupAdListener = new AdropPopupAdListener() {
@Override
public void onAdReceived(@NotNull AdropPopupAd ad) {
// Auto-show after receiving ad
if (context instanceof Activity) {
ad.show((Activity) context);
}
}
@Override
public void onAdFailedToReceive(@NotNull AdropPopupAd ad, @NotNull AdropErrorCode errorCode) {
Log.d("Adrop", "Ad failed to receive: " + errorCode);
}
};
private final AdropPopupAdCloseListener popupCloseListener = new AdropPopupAdCloseListener() {
@Override
public void onTodayOffClicked(@NotNull AdropPopupAd ad) {
// Save today's date
prefs.edit().putLong(TODAY_OFF_KEY, System.currentTimeMillis()).apply();
Log.d("Adrop", "Not showing popup ad today");
}
};
public void destroy() {
if (popupAd != null) {
popupAd.destroy();
popupAd = null;
}
}
}
```
### Usage Example
```kotlin Kotlin theme={null}
class MainActivity : AppCompatActivity() {
private lateinit var popupAdManager: PopupAdManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
popupAdManager = PopupAdManager(this)
// Show popup ad at app start
popupAdManager.loadAndShowAd(this, "YOUR_POPUP_UNIT_ID")
}
override fun onDestroy() {
super.onDestroy()
popupAdManager.destroy()
}
}
```
```java Java theme={null}
public class MainActivity extends AppCompatActivity {
private PopupAdManager popupAdManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
popupAdManager = new PopupAdManager(this);
// Show popup ad at app start
popupAdManager.loadAndShowAd(this, "YOUR_POPUP_UNIT_ID");
}
@Override
protected void onDestroy() {
super.onDestroy();
popupAdManager.destroy();
}
}
```
***
## Popup Types
Popup ads are available in two types:
| Type | Description | Position |
| - | - | - |
| `POPUP_BOTTOM` | Bottom popup | Slides up from bottom of screen |
| `POPUP_CENTER` | Center popup | Displayed at center of screen |
Popup type is configured when creating the ad unit in the Adrop console. No separate specification is needed in code.
***
## Best Practices
### 1. Appropriate Display Timing
Popup ads are effective when displayed at these moments:
```kotlin theme={null}
// Good example: At app launch
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
loadAndShowPopupAd()
}
// Good example: When content load completes
fun onContentLoaded() {
if (shouldShowPopupAd()) {
showPopupAd()
}
}
// Good example: When specific event completes
fun onAchievementUnlocked() {
showPopupAd()
}
// Bad example: When user is working
fun onUserTyping() {
showPopupAd() // Degrades user experience
}
```
### 2. Respect "Don't Show Today"
If a user selects "Don't show today," always honor it.
```kotlin theme={null}
fun shouldShowPopupAd(): Boolean {
// Check "Don't show today"
if (!canShowAdToday()) {
return false
}
// Check other conditions
return true
}
```
### 3. Frequency Limiting
Don't show popup ads too frequently.
```kotlin Kotlin theme={null}
class PopupAdFrequencyManager(private val context: Context) {
private val prefs = context.getSharedPreferences("adrop_prefs", Context.MODE_PRIVATE)
private val lastShownKey = "popup_ad_last_shown"
private val minimumInterval = 3600000L // 1 hour (milliseconds)
fun canShowAd(): Boolean {
// Check "Don't show today"
if (!canShowAdToday()) {
return false
}
// Check minimum time interval
val lastShown = prefs.getLong(lastShownKey, 0L)
if (lastShown == 0L) return true
val currentTime = System.currentTimeMillis()
return currentTime - lastShown >= minimumInterval
}
fun recordAdShown() {
prefs.edit().putLong(lastShownKey, System.currentTimeMillis()).apply()
}
private fun canShowAdToday(): Boolean {
// Refer to "Don't show today" implementation above
return true
}
}
```
```java Java theme={null}
public class PopupAdFrequencyManager {
private SharedPreferences prefs;
private static final String PREFS_NAME = "adrop_prefs";
private static final String LAST_SHOWN_KEY = "popup_ad_last_shown";
private static final long MINIMUM_INTERVAL = 3600000L; // 1 hour (milliseconds)
public PopupAdFrequencyManager(Context context) {
this.prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
}
public boolean canShowAd() {
// Check "Don't show today"
if (!canShowAdToday()) {
return false;
}
// Check minimum time interval
long lastShown = prefs.getLong(LAST_SHOWN_KEY, 0L);
if (lastShown == 0L) return true;
long currentTime = System.currentTimeMillis();
return currentTime - lastShown >= MINIMUM_INTERVAL;
}
public void recordAdShown() {
prefs.edit().putLong(LAST_SHOWN_KEY, System.currentTimeMillis()).apply();
}
private boolean canShowAdToday() {
// Refer to "Don't show today" implementation above
return true;
}
}
```
### 4. Preload Ads
Preload ads for better user experience.
```kotlin theme={null}
class MainActivity : AppCompatActivity() {
private var popupAd: AdropPopupAd? = null
private var isAdReady = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Preload
preloadPopupAd()
}
private fun preloadPopupAd() {
popupAd = AdropPopupAd(this, "YOUR_POPUP_UNIT_ID")
popupAd?.popupAdListener = object : AdropPopupAdListener {
override fun onAdReceived(ad: AdropPopupAd) {
isAdReady = true
}
override fun onAdFailedToReceive(ad: AdropPopupAd, errorCode: AdropErrorCode) {
Log.d("Adrop", "Ad failed to receive: $errorCode")
}
}
popupAd?.load()
}
fun showPopupAdIfReady() {
if (isAdReady) {
popupAd?.show(this)
}
}
}
```
### 5. Landscape Mode Handling
Popup ads are only supported in portrait mode. In landscape mode, `onAdFailedToShowFullScreen` is called.
```kotlin theme={null}
private val popupAdListener = object : AdropPopupAdListener {
override fun onAdReceived(ad: AdropPopupAd) {
// Show only in portrait mode
if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
ad.show(this@MainActivity)
}
}
override fun onAdFailedToShowFullScreen(ad: AdropPopupAd, errorCode: AdropErrorCode) {
if (errorCode == AdropErrorCode.ERROR_CODE_LANDSCAPE_UNSUPPORTED) {
Log.d("Adrop", "Popup ads are only supported in portrait mode")
}
}
override fun onAdFailedToReceive(ad: AdropPopupAd, errorCode: AdropErrorCode) {
Log.d("Adrop", "Ad failed to receive: $errorCode")
}
}
```
***
## Test Unit IDs
Use the following test unit IDs during development and testing.
| Ad Type | Test Unit ID |
| - | - |
| Popup (Bottom Image) | `PUBLIC_TEST_UNIT_ID_POPUP_BOTTOM` |
| Popup (Center Image) | `PUBLIC_TEST_UNIT_ID_POPUP_CENTER` |
| Popup Video (Bottom 16:9) | `PUBLIC_TEST_UNIT_ID_POPUP_BOTTOM_VIDEO_16_9` |
| Popup Video (Bottom 9:16) | `PUBLIC_TEST_UNIT_ID_POPUP_BOTTOM_VIDEO_9_16` |
| Popup Video (Center 16:9) | `PUBLIC_TEST_UNIT_ID_POPUP_CENTER_VIDEO_16_9` |
| Popup Video (Center 9:16) | `PUBLIC_TEST_UNIT_ID_POPUP_CENTER_VIDEO_9_16` |
### Test Ad Usage Example
```kotlin Kotlin theme={null}
// Separate test and production environments
val popupUnitId = if (BuildConfig.DEBUG) {
"PUBLIC_TEST_UNIT_ID_POPUP_BOTTOM"
} else {
"YOUR_PRODUCTION_UNIT_ID"
}
popupAd = AdropPopupAd(this, popupUnitId)
```
```java Java theme={null}
// Separate test and production environments
String popupUnitId;
if (BuildConfig.DEBUG) {
popupUnitId = "PUBLIC_TEST_UNIT_ID_POPUP_BOTTOM";
} else {
popupUnitId = "YOUR_PRODUCTION_UNIT_ID";
}
popupAd = new AdropPopupAd(this, popupUnitId);
```
Be sure to use the actual unit ID created in the Adrop console for production deployment.
***
## Complete Example
```kotlin Kotlin theme={null}
import android.graphics.Color
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import io.adrop.ads.model.AdropErrorCode
import io.adrop.ads.popupAd.AdropPopupAd
import io.adrop.ads.popupAd.AdropPopupAdCloseListener
import io.adrop.ads.popupAd.AdropPopupAdListener
class PopupAdActivity : AppCompatActivity() {
private var popupAd: AdropPopupAd? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_popup_ad)
// Load and show ad on button click
findViewById(R.id.btn_show_popup).setOnClickListener {
loadPopupAd()
}
}
private fun loadPopupAd() {
// Cleanup existing ad
popupAd?.destroy()
// Create popup ad
popupAd = AdropPopupAd(this, "YOUR_POPUP_UNIT_ID")
// Customization
popupAd?.apply {
backgroundColor = Color.parseColor("#CC000000")
hideForTodayTextColor = Color.WHITE
closeTextColor = Color.WHITE
ctaTextColor = Color.BLUE
useCustomClick = false
}
// Set listeners
popupAd?.popupAdListener = object : AdropPopupAdListener {
override fun onAdReceived(ad: AdropPopupAd) {
Log.d("Adrop", "Popup ad received")
showPopupAd()
}
override fun onAdFailedToReceive(ad: AdropPopupAd, errorCode: AdropErrorCode) {
Log.d("Adrop", "Popup ad failed to receive: $errorCode")
}
override fun onAdImpression(ad: AdropPopupAd) {
Log.d("Adrop", "Popup ad impression")
Log.d("Adrop", "txId: ${ad.txId}, campaignId: ${ad.campaignId}")
}
override fun onAdClicked(ad: AdropPopupAd) {
Log.d("Adrop", "Popup ad clicked: ${ad.destinationURL}")
}
override fun onAdDidPresentFullScreen(ad: AdropPopupAd) {
Log.d("Adrop", "Popup ad presented")
}
override fun onAdDidDismissFullScreen(ad: AdropPopupAd) {
Log.d("Adrop", "Popup ad dismissed")
// Preload next ad
loadPopupAd()
}
override fun onAdFailedToShowFullScreen(ad: AdropPopupAd, errorCode: AdropErrorCode) {
Log.d("Adrop", "Popup ad failed to show: $errorCode")
}
}
popupAd?.closeListener = object : AdropPopupAdCloseListener {
override fun onClosed(ad: AdropPopupAd) {
Log.d("Adrop", "Close button clicked")
}
override fun onDimClicked(ad: AdropPopupAd) {
Log.d("Adrop", "Dim area clicked")
}
override fun onTodayOffClicked(ad: AdropPopupAd) {
Log.d("Adrop", "Don't show today selected")
saveHiddenDate()
}
}
// Load ad
popupAd?.load()
}
private fun showPopupAd() {
popupAd?.let { ad ->
if (ad.isLoaded) {
ad.show(this)
}
}
}
private fun saveHiddenDate() {
val prefs = getSharedPreferences("adrop_prefs", MODE_PRIVATE)
prefs.edit().putLong("last_popup_hidden_date", System.currentTimeMillis()).apply()
}
override fun onDestroy() {
super.onDestroy()
popupAd?.destroy()
}
}
```
```java Java theme={null}
import android.graphics.Color;
import android.os.Bundle;
import android.util.Log;
import android.widget.Button;
import androidx.appcompat.app.AppCompatActivity;
import io.adrop.ads.model.AdropErrorCode;
import io.adrop.ads.popupAd.AdropPopupAd;
import io.adrop.ads.popupAd.AdropPopupAdCloseListener;
import io.adrop.ads.popupAd.AdropPopupAdListener;
import org.jetbrains.annotations.NotNull;
public class PopupAdActivity extends AppCompatActivity {
private AdropPopupAd popupAd;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_popup_ad);
// Load and show ad on button click
findViewById(R.id.btn_show_popup).setOnClickListener(v -> loadPopupAd());
}
private void loadPopupAd() {
// Cleanup existing ad
if (popupAd != null) {
popupAd.destroy();
}
// Create popup ad
popupAd = new AdropPopupAd(this, "YOUR_POPUP_UNIT_ID");
// Customization
popupAd.setBackgroundColor(Color.parseColor("#CC000000"));
popupAd.setHideForTodayTextColor(Color.WHITE);
popupAd.setCloseTextColor(Color.WHITE);
popupAd.setCtaTextColor(Color.BLUE);
popupAd.setUseCustomClick(false);
// Set listeners
popupAd.setPopupAdListener(new AdropPopupAdListener() {
@Override
public void onAdReceived(@NotNull AdropPopupAd ad) {
Log.d("Adrop", "Popup ad received");
showPopupAd();
}
@Override
public void onAdFailedToReceive(@NotNull AdropPopupAd ad, @NotNull AdropErrorCode errorCode) {
Log.d("Adrop", "Popup ad failed to receive: " + errorCode);
}
@Override
public void onAdImpression(@NotNull AdropPopupAd ad) {
Log.d("Adrop", "Popup ad impression");
Log.d("Adrop", "txId: " + ad.getTxId() + ", campaignId: " + ad.getCampaignId());
}
@Override
public void onAdClicked(@NotNull AdropPopupAd ad) {
Log.d("Adrop", "Popup ad clicked: " + ad.getDestinationURL());
}
@Override
public void onAdDidPresentFullScreen(@NotNull AdropPopupAd ad) {
Log.d("Adrop", "Popup ad presented");
}
@Override
public void onAdDidDismissFullScreen(@NotNull AdropPopupAd ad) {
Log.d("Adrop", "Popup ad dismissed");
// Preload next ad
loadPopupAd();
}
@Override
public void onAdFailedToShowFullScreen(@NotNull AdropPopupAd ad, @NotNull AdropErrorCode errorCode) {
Log.d("Adrop", "Popup ad failed to show: " + errorCode);
}
});
popupAd.setCloseListener(new AdropPopupAdCloseListener() {
@Override
public void onClosed(@NotNull AdropPopupAd ad) {
Log.d("Adrop", "Close button clicked");
}
@Override
public void onDimClicked(@NotNull AdropPopupAd ad) {
Log.d("Adrop", "Dim area clicked");
}
@Override
public void onTodayOffClicked(@NotNull AdropPopupAd ad) {
Log.d("Adrop", "Don't show today selected");
saveHiddenDate();
}
});
// Load ad
popupAd.load();
}
private void showPopupAd() {
if (popupAd != null && popupAd.isLoaded()) {
popupAd.show(this);
}
}
private void saveHiddenDate() {
getSharedPreferences("adrop_prefs", MODE_PRIVATE)
.edit()
.putLong("last_popup_hidden_date", System.currentTimeMillis())
.apply();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (popupAd != null) {
popupAd.destroy();
}
}
}
```
***
## Troubleshooting
### Ad Not Displaying
* Verify SDK is initialized
* Check if unit ID is correct
* Check network connection status
* Verify `production` setting during Adrop SDK initialization
* Check error code to identify cause
* Use test unit ID in test environment
* Retry later if ad inventory is insufficient
* `ERROR_CODE_AD_HIDE_FOR_TODAY`: "Don't show today" is active
* Verify `show()` is called after `onAdReceived` callback
* Check if Activity is in valid state
* Check `isLoaded` property to verify ad is loaded
* Verify in portrait mode (landscape is not supported)
Popup ads are only supported in portrait mode. When `show()` is called in landscape mode, `onAdFailedToShowFullScreen` is called with `ERROR_CODE_LANDSCAPE_UNSUPPORTED` error code.
### "Don't Show Today" Not Working
```kotlin theme={null}
// Verify date comparison logic
fun isToday(timestamp: Long): Boolean {
val calendar = Calendar.getInstance()
val today = calendar.apply {
timeInMillis = System.currentTimeMillis()
}
val targetDay = Calendar.getInstance().apply {
timeInMillis = timestamp
}
return today.get(Calendar.YEAR) == targetDay.get(Calendar.YEAR) &&
today.get(Calendar.DAY_OF_YEAR) == targetDay.get(Calendar.DAY_OF_YEAR)
}
// Usage
fun canShowAdToday(): Boolean {
val prefs = getSharedPreferences("adrop_prefs", Context.MODE_PRIVATE)
val lastHiddenDate = prefs.getLong("last_popup_hidden_date", 0L)
return lastHiddenDate == 0L || !isToday(lastHiddenDate)
}
```
***
## Related Documentation
SDK installation and initialization
Implement interstitial ads
Implement rewarded ads
Implement banner ads
# Reference
Source: https://docs.adrop.io/sdk/android/reference
Detailed reference for all classes, interfaces, and enums in the Adrop Android SDK.
## Adrop
The main class of the Adrop SDK. Handles SDK initialization and global settings.
### Static Methods
| Method | Description |
| - | - |
| `initialize(context: Application, production: Boolean)` | Initializes the SDK. |
| `initialize(context: Application, production: Boolean, targetCountries: Array)` | Initializes the SDK with target countries. |
| `initialize(context: Application, production: Boolean, targetCountries: Array, tokenKey: String?)` | Initializes the SDK with target countries and token key. |
| `setUID(uid: String)` | Sets the user identifier. (Hashed with SHA-256) |
| `setTheme(theme: AdropTheme)` | Sets the ad theme. |
| `setTokenKey(tokenKey: String)` | Sets the app token key. |
| `setMarketingConsent(consent: Boolean)` | Sets the user's marketing consent for push notification ad targeting. Call after `initialize()`. |
| `handleDeepLink(intent: Intent): Boolean` | Handles Adrop deep links. |
| `openQuest(context: Context, channel: String, path: String?)` | Opens the Adrop Quest page. |
| `registerWebView(webView: WebView)` | Registers a WebView for backfill ads support. Requires `adrop-ads-backfill` module. Call on the main thread. |
### Static Properties
| Property | Type | Description |
| - | - | - |
| `isProduction` | `Boolean` | Whether in production mode |
| `sdkVersion` | `String` | SDK version |
| `consentManager` | `AdropConsentManager?` | Consent manager instance (auto-configured when `adrop-ads-backfill` is installed) |
### Usage Example
```kotlin theme={null}
// Initialization
Adrop.initialize(application, production = true)
// Set user ID
Adrop.setUID("user123")
// Set theme
Adrop.setTheme(AdropTheme.DARK)
// Handle deep link
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
intent?.let { Adrop.handleDeepLink(it) }
}
```
***
## AdropTheme
Enum for setting dark mode for ads.
### Values
| Value | Description |
| - | - |
| `LIGHT` | Light mode |
| `DARK` | Dark mode |
| `AUTO` | Follow system settings automatically (default) |
### Usage Example
```kotlin theme={null}
Adrop.setTheme(AdropTheme.DARK)
```
***
## AdropErrorCode
Error codes that may occur when loading and displaying ads.
### Values
| Error Code | Description |
| - | - |
| `ERROR_CODE_NETWORK` | Network error |
| `ERROR_CODE_INTERNAL` | Internal error |
| `ERROR_CODE_INITIALIZE` | SDK initialization error |
| `ERROR_CODE_INVALID_UNIT` | Invalid ad unit ID |
| `ERROR_CODE_NOT_TARGET_COUNTRY` | Not in target country |
| `ERROR_CODE_AD_INACTIVE` | Inactive ad |
| `ERROR_CODE_AD_NO_FILL` | No ad available to display |
| `ERROR_CODE_AD_BACKFILL_NO_FILL` | No backfill ad available |
| `ERROR_CODE_AD_LOAD_DUPLICATED` | Duplicate ad load request |
| `ERROR_CODE_AD_LOADING` | Ad is loading |
| `ERROR_CODE_AD_EMPTY` | Ad is empty |
| `ERROR_CODE_AD_SHOWN` | Ad has already been shown |
| `ERROR_CODE_AD_HIDE_FOR_TODAY` | "Don't show today" is set |
| `ERROR_CODE_ACCOUNT_USAGE_LIMIT_EXCEEDED` | Account usage limit exceeded |
| `ERROR_CODE_LANDSCAPE_UNSUPPORTED` | Landscape mode not supported |
***
## AdropAdChoicesPosition
Enum for setting the AdChoices badge position on backfill native ads.
### Values
| Value | Description |
| - | - |
| `TOP_LEFT` | Top-left corner |
| `TOP_RIGHT` | Top-right corner (default) |
| `BOTTOM_LEFT` | Bottom-left corner |
| `BOTTOM_RIGHT` | Bottom-right corner |
### Usage Example
```kotlin theme={null}
val nativeAd = AdropNativeAd(this, "YOUR_UNIT_ID")
nativeAd.preferredAdChoicesPosition = AdropAdChoicesPosition.BOTTOM_RIGHT
nativeAd.load()
```
***
## AdropBanner
View class for displaying banner ads.
### Constructor
```kotlin theme={null}
// Use in XML
AdropBanner(context: Context, attrs: AttributeSet?)
// Use in code
AdropBanner(context: Context, unitId: String, contextId: String? = null)
```
### Properties
| Property | Type | Description |
| - | - | - |
| `listener` | `AdropBannerListener?` | Banner event listener |
| `shouldAdjustHtmlStyle` | `Boolean` | Whether to auto-adjust HTML style (default: false) |
| `useCustomClick` | `Boolean` | Whether to use custom click handling (default: false) |
| `adSize` | `CreativeSize?` | Banner size for backfill ads (dp) |
| `contextId` | `String` | Context ID |
| `creativeId` | `String` | Creative ID |
| `txId` | `String` | Transaction ID |
| `campaignId` | `String` | Campaign ID |
| `destinationURL` | `String` | Destination URL |
| `creativeSize` | `CreativeSize` | Creative size |
| `creativeType` | `String` | Loaded creative type (`"display"` or `"video"`). Returns `"display"` until an ad response is received. |
| `isBackfilled` | `Boolean` | Whether it's a backfill ad |
| `browserTarget` | `Int` | Browser target for opening destination URL |
### Methods
| Method | Description |
| - | - |
| `setUnitId(unitId: String)` | Set ad unit ID |
| `getUnitId(): String` | Get ad unit ID |
| `load()` | Load ad |
| `open(url: String?)` | Open ad landing page |
| `play()` | Start or resume video banner playback |
| `pause()` | Pause video banner playback |
| `destroy()` | Destroy banner ad |
### Companion Methods
| Method | Description |
| - | - |
| `loads(context, unitId, contextId?, listener)` | Batch load up to 5 banner ads — see [Batch Loading](/sdk/android/banner#batch-loading-loads) |
### Usage Example
```kotlin theme={null}
val banner = AdropBanner(this, "YOUR_UNIT_ID")
banner.listener = object : AdropBannerListener {
override fun onAdReceived(banner: AdropBanner) {
// Ad received successfully
}
override fun onAdFailedToReceive(banner: AdropBanner, errorCode: AdropErrorCode) {
// Failed to receive ad
}
override fun onAdClicked(banner: AdropBanner) {
// Ad clicked
}
}
// Load ad
banner.load()
```
***
## AdropBannerListener
Interface for handling banner ad events.
### Methods
| Method | Description |
| - | - |
| `onAdReceived(banner: AdropBanner)` | Ad received successfully |
| `onAdClicked(banner: AdropBanner)` | Ad clicked |
| `onAdImpression(banner: AdropBanner)` | Ad impression (optional implementation) |
| `onAdFailedToReceive(banner: AdropBanner, errorCode: AdropErrorCode)` | Failed to receive ad |
| `onAdVideoStart(banner: AdropBanner)` | Video playback started (optional implementation) |
| `onAdVideoEnd(banner: AdropBanner)` | Video playback ended (optional implementation) |
| `onAdsReceived(banners: List)` | Batch ads received from `AdropBanner.loads()` (optional implementation) |
| `onAdsFailedToReceive(errorCode: AdropErrorCode)` | Batch load failed from `AdropBanner.loads()` (optional implementation) |
All callbacks are called on the main thread (`@UiThread`).
***
## AdropNativeAd
Class for displaying native ads.
### Constructor
```kotlin theme={null}
AdropNativeAd(context: Context, unitId: String, contextId: String? = null)
```
### Properties
| Property | Type | Description |
| - | - | - |
| `unitId` | `String` | Ad unit ID |
| `contextId` | `String?` | Context ID |
| `listener` | `AdropNativeAdListener?` | Native ad event listener |
| `useCustomClick` | `Boolean` | Whether to use custom click handling |
| `preferredAdChoicesPosition` | `AdropAdChoicesPosition` | AdChoices badge position for backfill native ads (default: `TOP_RIGHT`) |
| `context` | `Context` | Context |
| `isLoaded` | `Boolean` | Whether ad loading is complete |
| `isDestroyed` | `Boolean` | Whether ad is destroyed |
| `creativeId` | `String` | Creative ID |
| `txId` | `String` | Transaction ID |
| `campaignId` | `String` | Campaign ID |
| `creativeSize` | `CreativeSize` | Creative size |
| `icon` | `String` | Icon image URL |
| `cover` | `String` | Cover image URL |
| `headline` | `String` | Headline |
| `body` | `String` | Body text |
| `creative` | `String` | Creative URL |
| `asset` | `String` | Asset URL |
| `destinationURL` | `String` | Destination URL |
| `advertiser` | `String` | **Deprecated.** Use `profile.displayName` instead. |
| `advertiserURL` | `String` | **Deprecated.** Use `profile.link` instead. |
| `accountTag` | `JSONObject` | **Deprecated.** No longer supported. |
| `creativeTag` | `JSONObject` | **Deprecated.** No longer supported. |
| `callToAction` | `String` | Call to action text |
| `extra` | `JSONObject` | Extra data |
| `profile` | `AdropNativeAdProfile` | Profile information |
| `creativeType` | `String` | Loaded creative type (`"display"` or `"video"`). Returns `"display"` until an ad response is received. |
| `isBackfilled` | `Boolean` | Whether it's a backfill ad |
| `browserTarget` | `Int` | Browser target for opening destination URL |
### Methods
| Method | Description |
| - | - |
| `load()` | Load ad |
| `destroy()` | Destroy ad |
### Companion Methods
| Method | Description |
| - | - |
| `loads(context, unitId, contextId?, listener)` | Batch load up to 5 native ads — see [Batch Loading](/sdk/android/native#batch-loading-loads) |
### Usage Example
```kotlin theme={null}
val nativeAd = AdropNativeAd(this, "YOUR_UNIT_ID")
nativeAd.listener = object : AdropNativeAdListener {
override fun onAdReceived(ad: AdropNativeAd) {
// Bind ad data
headlineTextView.text = ad.headline
bodyTextView.text = ad.body
advertiserTextView.text = ad.advertiser
ctaButton.text = ad.callToAction
// Set ad to view
nativeAdView.setNativeAd(ad)
}
override fun onAdFailedToReceive(ad: AdropNativeAd, errorCode: AdropErrorCode) {
// Failed to receive ad
}
override fun onAdClicked(ad: AdropNativeAd) {
// Ad clicked
}
}
nativeAd.load()
```
***
## AdropNativeAdView
Container view for rendering native ads.
### Constructor
```kotlin theme={null}
AdropNativeAdView(context: Context, attrs: AttributeSet?)
```
### Properties
| Property | Type | Description |
| - | - | - |
| `isEntireClick` | `Boolean` | Whether entire area is clickable |
### Methods
| Method | Description |
| - | - |
| `setIconView(view: View, listener: OnClickListener?)` | Set icon view (ImageView only) |
| `setHeadLineView(view: View, listener: OnClickListener?)` | Set headline view (TextView only) |
| `setBodyView(view: View)` | Set body view (TextView only) |
| `setMediaView(view: AdropMediaView)` | Set media view |
| `setAdvertiserView(view: View, listener: OnClickListener?)` | Set advertiser view (TextView only) |
| `setCallToActionView(view: View)` | Set call to action button view |
| `setProfileNameView(view: View, listener: OnClickListener?)` | Set profile name view |
| `setProfileLogoView(view: View, listener: OnClickListener?)` | Set profile logo view (ImageView only) |
| `setNativeAd(ad: AdropNativeAd)` | Set native ad |
| `destroy()` | Destroy view |
### Usage Example
```kotlin theme={null}
// Reference views from layout
val nativeAdView = findViewById(R.id.native_ad_view)
val iconView = findViewById(R.id.ad_icon)
val headlineView = findViewById(R.id.ad_headline)
val bodyView = findViewById(R.id.ad_body)
val mediaView = findViewById(R.id.ad_media)
val advertiserView = findViewById(R.id.ad_advertiser)
val ctaView = findViewById(R.id.ad_cta)
// Set views
nativeAdView.setIconView(iconView)
nativeAdView.setHeadLineView(headlineView)
nativeAdView.setBodyView(bodyView)
nativeAdView.setMediaView(mediaView)
nativeAdView.setAdvertiserView(advertiserView)
nativeAdView.setCallToActionView(ctaView)
// Set after ad loads
nativeAd.listener = object : AdropNativeAdListener {
override fun onAdReceived(ad: AdropNativeAd) {
nativeAdView.setNativeAd(ad)
}
}
```
***
## AdropNativeAdListener
Interface for handling native ad events.
### Methods
| Method | Description |
| - | - |
| `onAdReceived(ad: AdropNativeAd)` | Ad received successfully |
| `onAdClicked(ad: AdropNativeAd)` | Ad clicked |
| `onAdImpression(ad: AdropNativeAd)` | Ad impression (optional implementation) |
| `onAdFailedToReceive(ad: AdropNativeAd, errorCode: AdropErrorCode)` | Failed to receive ad |
| `onAdVideoStart(ad: AdropNativeAd)` | Video playback started (optional implementation) |
| `onAdVideoEnd(ad: AdropNativeAd)` | Video playback ended (optional implementation) |
| `onAdsReceived(ads: List)` | Batch ads received from `AdropNativeAd.loads()` (optional implementation) |
| `onAdsFailedToReceive(errorCode: AdropErrorCode)` | Batch load failed from `AdropNativeAd.loads()` (optional implementation) |
All callbacks are called on the main thread (`@UiThread`).
***
## AdropNativeAdProfile
Data class containing profile information for native ads.
### Properties
| Property | Type | Description |
| - | - | - |
| `displayLogo` | `String` | Profile logo image URL |
| `displayName` | `String` | Profile name |
| `link` | `String` | Profile link URL |
### Usage Example
```kotlin theme={null}
val profile = nativeAd.profile
profileNameTextView.text = profile.displayName
// Load profile logo into ImageView
```
***
## AdropMediaView
View for displaying native ad media content.
### Constructor
```kotlin theme={null}
AdropMediaView(context: Context, attrs: AttributeSet?)
```
### Description
The media view displays image or video content for native ads. It should be set through the `setMediaView()` method of `AdropNativeAdView`.
***
## AdropInterstitialAd
Class for displaying interstitial ads.
### Constructor
```kotlin theme={null}
AdropInterstitialAd(context: Context, unitId: String)
```
### Properties
| Property | Type | Description |
| - | - | - |
| `interstitialAdListener` | `AdropInterstitialAdListener?` | Interstitial ad event listener |
| `closeListener` | `AdropInterstitialAdCloseListener?` | Interstitial ad close event listener |
| `context` | `Context?` | Context |
| `unitId` | `String` | Ad unit ID |
| `isLoaded` | `Boolean` | Whether ad loading is complete |
| `creativeId` | `String` | Creative ID |
| `txId` | `String` | Transaction ID |
| `campaignId` | `String` | Campaign ID |
| `isBackfilled` | `Boolean` | Whether it's a backfill ad |
| `browserTarget` | `Int` | Browser target for opening destination URL |
### Methods
| Method | Description |
| - | - |
| `load()` | Load ad |
| `show(fromActivity: Activity)` | Show ad |
| `close()` | Programmatically close the interstitial ad |
| `destroy()` | Destroy ad |
### Usage Example
```kotlin theme={null}
val interstitialAd = AdropInterstitialAd(this, "YOUR_UNIT_ID")
interstitialAd.interstitialAdListener = object : AdropInterstitialAdListener {
override fun onAdReceived(ad: AdropInterstitialAd) {
// Ad loaded, ready to show
ad.show(this@MainActivity)
}
override fun onAdFailedToReceive(ad: AdropInterstitialAd, errorCode: AdropErrorCode) {
// Failed to receive ad
}
override fun onAdDidDismissFullScreen(ad: AdropInterstitialAd) {
// Ad closed
}
}
// Load ad
interstitialAd.load()
```
***
## AdropInterstitialAdListener
Interface for handling interstitial ad events.
### Methods
| Method | Description |
| - | - |
| `onAdReceived(ad: AdropInterstitialAd)` | Ad received successfully |
| `onAdFailedToReceive(ad: AdropInterstitialAd, errorCode: AdropErrorCode)` | Failed to receive ad |
| `onAdImpression(ad: AdropInterstitialAd)` | Ad impression (optional implementation) |
| `onAdClicked(ad: AdropInterstitialAd)` | Ad clicked (optional implementation) |
| `onAdWillPresentFullScreen(ad: AdropInterstitialAd)` | Full screen ad will present (optional implementation) |
| `onAdDidPresentFullScreen(ad: AdropInterstitialAd)` | Full screen ad did present (optional implementation) |
| `onAdWillDismissFullScreen(ad: AdropInterstitialAd)` | Full screen ad will dismiss (optional implementation) |
| `onAdDidDismissFullScreen(ad: AdropInterstitialAd)` | Full screen ad did dismiss (optional implementation) |
| `onAdFailedToShowFullScreen(ad: AdropInterstitialAd, errorCode: AdropErrorCode)` | Failed to show ad (optional implementation) |
All callbacks are called on the main thread (`@UiThread`).
***
## AdropInterstitialAdCloseListener
Interface for handling interstitial ad close events.
### Methods
| Method | Description |
| - | - |
| `onBackPressed(ad: AdropInterstitialAd)` | Called when the back button is pressed while the interstitial ad is displayed (optional implementation) |
All callbacks are called on the main thread (`@UiThread`).
***
## AdropRewardedAd
Class for displaying rewarded ads.
### Constructor
```kotlin theme={null}
AdropRewardedAd(context: Context, unitId: String)
```
### Properties
| Property | Type | Description |
| - | - | - |
| `rewardedAdListener` | `AdropRewardedAdListener?` | Rewarded ad event listener |
| `context` | `Context?` | Context |
| `unitId` | `String` | Ad unit ID |
| `isLoaded` | `Boolean` | Whether ad loading is complete |
| `creativeId` | `String` | Creative ID |
| `txId` | `String` | Transaction ID |
| `campaignId` | `String` | Campaign ID |
| `isBackfilled` | `Boolean` | Whether it's a backfill ad |
| `serverSideVerificationOptions` | `ServerSideVerificationOptions?` | Server-side verification options (userId, customData) |
| `browserTarget` | `Int` | Browser target for opening destination URL |
### Methods
| Method | Description |
| - | - |
| `load()` | Load ad |
| `show(fromActivity: Activity, userDidEarnRewardHandler: AdropUserDidEarnRewardHandler)` | Show ad with reward handler |
| `destroy()` | Destroy ad |
### Usage Example
```kotlin theme={null}
val rewardedAd = AdropRewardedAd(this, "YOUR_UNIT_ID")
rewardedAd.rewardedAdListener = object : AdropRewardedAdListener {
override fun onAdReceived(ad: AdropRewardedAd) {
// Ad loaded
ad.show(this@MainActivity) { type, amount ->
// Grant reward
Log.d("Reward", "Type: $type, Amount: $amount")
}
}
override fun onAdFailedToReceive(ad: AdropRewardedAd, errorCode: AdropErrorCode) {
// Failed to receive ad
}
}
rewardedAd.load()
```
***
## AdropRewardedAdListener
Interface for handling rewarded ad events.
### Methods
| Method | Description |
| - | - |
| `onAdReceived(ad: AdropRewardedAd)` | Ad received successfully |
| `onAdFailedToReceive(ad: AdropRewardedAd, errorCode: AdropErrorCode)` | Failed to receive ad |
| `onAdImpression(ad: AdropRewardedAd)` | Ad impression (optional implementation) |
| `onAdClicked(ad: AdropRewardedAd)` | Ad clicked (optional implementation) |
| `onAdWillPresentFullScreen(ad: AdropRewardedAd)` | Full screen ad will present (optional implementation) |
| `onAdDidPresentFullScreen(ad: AdropRewardedAd)` | Full screen ad did present (optional implementation) |
| `onAdWillDismissFullScreen(ad: AdropRewardedAd)` | Full screen ad will dismiss (optional implementation) |
| `onAdDidDismissFullScreen(ad: AdropRewardedAd)` | Full screen ad did dismiss (optional implementation) |
| `onAdFailedToShowFullScreen(ad: AdropRewardedAd, errorCode: AdropErrorCode)` | Failed to show ad (optional implementation) |
All callbacks are called on the main thread (`@UiThread`).
***
## AdropUserDidEarnRewardHandler
Type alias called when a reward is granted.
### Definition
```kotlin theme={null}
typealias AdropUserDidEarnRewardHandler = (type: Int, amount: Int) -> Unit
```
### Parameters
| Parameter | Type | Description |
| - | - | - |
| `type` | `Int` | Reward type |
| `amount` | `Int` | Reward amount |
### Usage Example
```kotlin theme={null}
rewardedAd.show(this) { type, amount ->
// Grant reward to user
when (type) {
0 -> grantCoins(amount)
1 -> grantLives(amount)
}
}
```
***
## AdropPopupAd
Class for displaying popup ads.
### Constructor
```kotlin theme={null}
AdropPopupAd(context: Context, unitId: String)
```
### Properties
| Property | Type | Description |
| - | - | - |
| `popupAdListener` | `AdropPopupAdListener?` | Popup ad event listener |
| `closeListener` | `AdropPopupAdCloseListener?` | Popup close event listener |
| `useCustomClick` | `Boolean` | Whether to use custom click handling |
| `closeTextColor` | `Int?` | Close button text color |
| `hideForTodayTextColor` | `Int?` | "Don't show today" text color |
| `ctaTextColor` | `Int?` | CTA button text color |
| `backgroundColor` | `Int?` | Background color |
| `unitId` | `String` | Ad unit ID |
| `creativeIds` | `List` | Creative ID list |
| `creativeId` | `String` | Current creative ID |
| `creativeType` | `String` | Loaded creative type (`"display"` or `"video"`). Returns `"display"` until an ad response is received. |
| `destinationURL` | `String` | Destination URL |
| `isLoaded` | `Boolean` | Whether ad loading is complete |
| `txId` | `String` | Transaction ID |
| `campaignId` | `String` | Campaign ID |
| `browserTarget` | `Int` | Browser target for opening destination URL |
### Methods
| Method | Description |
| - | - |
| `load()` | Load ad |
| `show(activity: Activity)` | Show ad |
| `open(url: String?)` | Open ad landing page |
| `close()` | Close ad |
| `destroy()` | Destroy ad |
### Usage Example
```kotlin theme={null}
val popupAd = AdropPopupAd(this, "YOUR_UNIT_ID")
// Customize style
popupAd.closeTextColor = Color.WHITE
popupAd.backgroundColor = Color.parseColor("#80000000")
popupAd.popupAdListener = object : AdropPopupAdListener {
override fun onAdReceived(ad: AdropPopupAd) {
// Ad loaded
ad.show(this@MainActivity)
}
override fun onAdFailedToReceive(ad: AdropPopupAd, errorCode: AdropErrorCode) {
// Failed to receive ad
}
}
popupAd.closeListener = object : AdropPopupAdCloseListener {
override fun onTodayOffClicked(ad: AdropPopupAd) {
// "Don't show today" clicked
}
}
popupAd.load()
```
***
## AdropPopupAdListener
Interface for handling popup ad events.
### Methods
| Method | Description |
| - | - |
| `onAdReceived(ad: AdropPopupAd)` | Ad received successfully |
| `onAdFailedToReceive(ad: AdropPopupAd, errorCode: AdropErrorCode)` | Failed to receive ad |
| `onAdImpression(ad: AdropPopupAd)` | Ad impression (optional implementation) |
| `onAdClicked(ad: AdropPopupAd)` | Ad clicked (optional implementation) |
| `onAdWillPresentFullScreen(ad: AdropPopupAd)` | Full screen ad will present (optional implementation) |
| `onAdDidPresentFullScreen(ad: AdropPopupAd)` | Full screen ad did present (optional implementation) |
| `onAdWillDismissFullScreen(ad: AdropPopupAd)` | Full screen ad will dismiss (optional implementation) |
| `onAdDidDismissFullScreen(ad: AdropPopupAd)` | Full screen ad did dismiss (optional implementation) |
| `onAdFailedToShowFullScreen(ad: AdropPopupAd, errorCode: AdropErrorCode)` | Failed to show ad (optional implementation) |
| `onAdVideoStart(ad: AdropPopupAd)` | Video playback started (optional implementation) |
| `onAdVideoEnd(ad: AdropPopupAd)` | Video playback ended (optional implementation) |
All callbacks are called on the main thread (`@UiThread`).
***
## AdropPopupAdCloseListener
Interface for handling popup ad close events.
### Methods
| Method | Description |
| - | - |
| `onClosed(ad: AdropPopupAd)` | Popup closed (optional implementation) |
| `onDimClicked(ad: AdropPopupAd)` | Dim area clicked (optional implementation) |
| `onTodayOffClicked(ad: AdropPopupAd)` | "Don't show today" clicked (optional implementation) |
All callbacks are called on the main thread (`@UiThread`).
***
## AdropSplashAd
Class for displaying splash ads.
### Constructor
```kotlin theme={null}
AdropSplashAd(application: Application, shouldSkip: ((splashAd: AdropSplashAd) -> Boolean)? = null)
```
### Parameters
| Parameter | Type | Description |
| - | - | - |
| `application` | `Application` | Application instance |
| `shouldSkip` | `((AdropSplashAd) -> Boolean)?` | Function to determine whether to skip splash ad (optional) |
### Properties
| Property | Type | Description |
| - | - | - |
| `splashAdListener` | `AdropSplashAdListener?` | Splash ad event listener |
| `creativeId` | `String` | Creative ID |
| `isClosed` | `Boolean` | Whether ad is closed |
| `isDestroyed` | `Boolean` | Whether ad is destroyed |
| `unitId` | `String` | Ad unit ID |
### Methods
| Method | Description |
| - | - |
| `close()` | Close ad |
| `destroy()` | Release resources and unregister lifecycle callbacks |
### Usage Example
```kotlin theme={null}
// Need to add AdropSplashAdActivity to AndroidManifest.xml
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
val splashAd = AdropSplashAd(this) { ad ->
// Skip under certain conditions
false
}
splashAd.splashAdListener = object : AdropSplashAdListener {
override fun onAdReceived(ad: AdropSplashAd) {
// Ad received
}
override fun onAdClose(ad: AdropSplashAd, impressed: Boolean) {
// Ad closed
}
}
}
}
```
***
## AdropSplashAdListener
Interface for handling splash ad events.
### Methods
| Method | Description |
| - | - |
| `onAdReceived(ad: AdropSplashAd)` | Ad received successfully (optional implementation) |
| `onAdFailedToReceive(ad: AdropSplashAd, errorCode: AdropErrorCode)` | Failed to receive ad (optional implementation) |
| `onAdImpression(ad: AdropSplashAd)` | Ad impression (optional implementation) |
| `onAdClose(ad: AdropSplashAd, impressed: Boolean)` | Ad closed (required implementation) |
### Parameters
| Parameter | Type | Description |
| - | - | - |
| `impressed` | `Boolean` | Whether the ad was actually impressed |
All callbacks are called on the main thread (`@UiThread`).
***
## AdropSplashAdView
Class for displaying splash ads as a view.
### Constructor
```kotlin theme={null}
AdropSplashAdView(context: Context, unitId: String, adRequestTimeout: Long = 1000L)
AdropSplashAdView(context: Context, attrs: AttributeSet?)
```
### Properties
| Property | Type | Description |
| - | - | - |
| `listener` | `AdropSplashAdViewListener?` | Splash ad view event listener |
| `displayDuration` | `Long` | Display duration (Deprecated: Set in console) |
| `creativeId` | `String` | Creative ID |
| `isClosed` | `Boolean` | Whether ad is closed |
| `unitId` | `String` | Ad unit ID |
| `txId` | `String` | Transaction ID |
| `campaignId` | `String` | Campaign ID |
### Methods
| Method | Description |
| - | - |
| `load()` | Load ad |
| `destroy()` | Release resources (timers, visibility tracker, listener) |
### Usage Example
```kotlin theme={null}
val splashAdView = AdropSplashAdView(this, "YOUR_UNIT_ID", adRequestTimeout = 2000L)
splashAdView.listener = object : AdropSplashAdViewListener {
override fun onAdReceived(ad: AdropSplashAdView) {
// Ad received
}
override fun onAdClose(ad: AdropSplashAdView, impressed: Boolean) {
// Ad closed - navigate to main screen
if (impressed) {
// Ad was impressed
}
startMainActivity()
}
}
// Add to layout
container.addView(splashAdView)
splashAdView.load()
```
***
## AdropSplashAdViewListener
Interface for handling splash ad view events.
### Methods
| Method | Description |
| - | - |
| `onAdReceived(ad: AdropSplashAdView)` | Ad received successfully (optional implementation) |
| `onAdFailedToReceive(ad: AdropSplashAdView, errorCode: AdropErrorCode)` | Failed to receive ad (optional implementation) |
| `onAdImpression(ad: AdropSplashAdView)` | Ad impression (optional implementation) |
| `onAdClose(ad: AdropSplashAdView, impressed: Boolean)` | Ad closed (required implementation) |
All callbacks are called on the main thread (`@UiThread`).
***
## AdropKey
Class that defines user property keys.
### Constants
| Constant | Value | Description |
| - | - | - |
| `AGE` | `"AGE"` | Age |
| `BIRTH` | `"BIRTH"` | Date of birth |
| `GENDER` | `"GDR"` | Gender |
### Usage Example
```kotlin theme={null}
val params = AdropEventParam.Builder()
.putInt(AdropKey.AGE, 25)
.putString(AdropKey.GENDER, AdropValue.AdropGender.MALE)
.putString(AdropKey.BIRTH, "19990101")
.build()
```
***
## AdropValue
Class that defines user property values.
### Common Constants
| Constant | Value | Description |
| - | - | - |
| `UNKNOWN` | `"U"` | Unknown |
### AdropBirth
Defines date of birth formats.
| Constant | Value | Description |
| - | - | - |
| `formatYear` | `"yyyy"` | Year only (e.g., 1999) |
| `formatYearMonth` | `"yyyyMM"` | Year and month (e.g., 199901) |
| `formatYearMonthDay` | `"yyyyMMdd"` | Year, month, and day (e.g., 19990101) |
### AdropGender
Defines gender values.
| Constant | Value | Description |
| - | - | - |
| `MALE` | `"M"` | Male |
| `FEMALE` | `"F"` | Female |
| `OTHER` | `"O"` | Other |
### Usage Example
```kotlin theme={null}
val params = AdropEventParam.Builder()
.putString(AdropKey.GENDER, AdropValue.AdropGender.MALE)
.putString(AdropKey.BIRTH, "19990101") // Using formatYearMonthDay
.build()
```
***
## CreativeSize
Data class representing the size of a creative.
### Properties
| Property | Type | Description |
| - | - | - |
| `width` | `Double` | Width (dp) |
| `height` | `Double` | Height (dp) |
### Usage Example
```kotlin theme={null}
val banner = AdropBanner(this, "YOUR_UNIT_ID")
banner.adSize = CreativeSize(320.0, 50.0)
```
***
## AdropEventParam
Class for creating custom event parameters.
### Builder Methods
| Method | Description |
| - | - |
| `putBoolean(key: String, value: Boolean)` | Add Boolean value |
| `putInt(key: String, value: Int)` | Add Int value |
| `putFloat(key: String, value: Float)` | Add Float value |
| `putString(key: String, value: String)` | Add String value (max 1024 characters) |
| `putLong(key: String, value: Long)` | Add Long value (max: 9007199254740991) |
| `putItems(items: List)` | Add item list for multi-item events |
| `build()` | Create AdropEventParam instance |
### Usage Example
```kotlin theme={null}
val params = AdropEventParam.Builder()
.putString(AdropKey.GENDER, AdropValue.AdropGender.MALE)
.putInt(AdropKey.AGE, 25)
.putString(AdropKey.BIRTH, "19990101")
.build()
```
* Parameter key must be 1–64 characters.
* String values are limited to 1024 characters maximum.
* The value passed to `putLong()` cannot exceed JavaScript's MAX\_SAFE\_INTEGER (9007199254740991).
***
## AdropMetrics
Singleton object for managing user events and properties.
### Static Properties
| Property | Type | Description |
| - | - | - |
| `userDataConsent` | `Boolean` | Current user data consent status (default: `true`) |
| `properties` | `JSONObject` | Current custom user properties |
### Static Methods
| Method | Description |
| - | - |
| `sendEvent(name: String, params: AdropEventParam?)` | Send a user event |
| `logEvent(name: String, params: AdropEventParam?)` | **Deprecated.** Use `sendEvent` instead. |
| `setProperty(key: String, value: Any?)` | Set a user property for targeting |
| `setUserDataConsent(consent: Boolean)` | Set user data collection consent status. When `false`, server transmission is blocked (local storage is still allowed). |
`logEvent()` is deprecated and will be removed in a future version. Use `sendEvent()` instead.
### sendEvent
Sends a user event with optional parameters.
```kotlin theme={null}
// Simple event (no parameters)
AdropMetrics.sendEvent("app_open")
// Event with parameters
val params = AdropEventParam.Builder()
.putString("item_id", "SKU-123")
.putString("item_name", "Widget")
.putFloat("price", 29.99f)
.build()
AdropMetrics.sendEvent("view_item", params)
// Multi-item event (e.g., purchase)
val item1 = AdropEventParam.Builder()
.putString("item_id", "A")
.putString("item_name", "Product A")
.putInt("price", 100)
.putInt("quantity", 1)
.build()
val item2 = AdropEventParam.Builder()
.putString("item_id", "B")
.putString("item_name", "Product B")
.putInt("price", 200)
.putInt("quantity", 2)
.build()
val purchaseParams = AdropEventParam.Builder()
.putString("tx_id", "tx_123")
.putString("currency", "KRW")
.putItems(listOf(item1, item2))
.build()
AdropMetrics.sendEvent("purchase", purchaseParams)
```
* Event name must be 1–64 characters.
* Duplicate events with the same name and parameters are throttled (500ms window).
* The SDK must be initialized before calling `sendEvent()`.
***
## Backfill Revenue (`onPaidEvent`)
Callback that delivers revenue information when a backfill ad is served. It fires only for backfill ads and is never invoked for Adrop direct ads.
### Supported ad formats
Supported on Banner, Native, Interstitial, and Rewarded. Not supported on Popup or Splash.
### `AdropPaidEventListener`
```kotlin theme={null}
fun interface AdropPaidEventListener {
@UiThread
fun onPaidEvent(ad: T, value: AdropAdValue)
}
```
### `AdropAdValue`
| Field | Type | Description |
| - | - | - |
| `network` | `String` | Backfill network identifier (e.g., `admob`) |
| `adSourceName` | `String?` | Mediation source inside the network (e.g., `AppLovin`) |
| `valueMicros` | `Long` | Revenue in micro units (1/1,000,000 of `currencyCode`) |
| `currencyCode` | `String` | ISO 4217 currency code |
| `precision` | `AdropAdValuePrecision` | Precision (`UNKNOWN`, `ESTIMATED`, `PUBLISHER_PROVIDED`, `PRECISE`) |
| `value` | `Double` | Computed as `valueMicros / 1_000_000` |
### Usage
```kotlin theme={null}
val banner = AdropBanner(this, "YOUR_UNIT_ID")
banner.paidEventListener = AdropPaidEventListener { ad, value ->
analytics.track(ad.unitId, value.valueMicros, value.currencyCode)
}
banner.load()
```
`AdropNativeAd`, `AdropInterstitialAd`, and `AdropRewardedAd` also register via the same `paidEventListener` property. The callback runs on the main thread (`@UiThread`).
# Rewarded Ads
Source: https://docs.adrop.io/sdk/android/rewarded
Learn how to implement rewarded video ads in your Android app.
## Overview
Rewarded ads are full-screen video ads that provide users with rewards such as in-game currency, lives, or hints when they watch the ad to completion.
### Key Features
* Full-screen video ads
* Rewards provided only when users watch the entire ad
* User-initiated ad viewing (e.g., "Watch video to get lives" button)
* Configurable reward type and amount
Use the test unit ID for development: `PUBLIC_TEST_UNIT_ID_REWARDED`
***
## Implementation Steps
Rewarded ads are implemented in the following steps:
1. **Create AdropRewardedAd Instance** - Create ad object with unit ID
2. **Set Listener** - Configure listener for ad events
3. **Load Ad** - Request ad with `load()` method
4. **Show Ad** - Display ad with `show()` method and handle rewards
***
## Basic Implementation
### Kotlin Example
```kotlin theme={null}
import io.adrop.ads.rewardedAd.AdropRewardedAd
import io.adrop.ads.rewardedAd.AdropRewardedAdListener
import io.adrop.ads.model.AdropErrorCode
class RewardedActivity : AppCompatActivity() {
private var rewardedAd: AdropRewardedAd? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_rewarded)
// 1. Create AdropRewardedAd instance
rewardedAd = AdropRewardedAd(this, "YOUR_UNIT_ID")
// 2. Set Listener
rewardedAd?.rewardedAdListener = object : AdropRewardedAdListener {
// Required: Ad received successfully
override fun onAdReceived(ad: AdropRewardedAd) {
Log.d(TAG, "Ad received")
// Enable watch ad button, etc.
}
// Required: Ad failed to receive
override fun onAdFailedToReceive(ad: AdropRewardedAd, errorCode: AdropErrorCode) {
Log.d(TAG, "Ad failed to receive: $errorCode")
// Keep watch ad button disabled, etc.
}
// Optional: Ad impression
override fun onAdImpression(ad: AdropRewardedAd) {
Log.d(TAG, "Ad impression")
}
// Optional: Ad clicked
override fun onAdClicked(ad: AdropRewardedAd) {
Log.d(TAG, "Ad clicked")
}
// Optional: Before showing full screen
override fun onAdWillPresentFullScreen(ad: AdropRewardedAd) {
Log.d(TAG, "Ad will present")
// Pause background music, etc.
}
// Optional: After showing full screen
override fun onAdDidPresentFullScreen(ad: AdropRewardedAd) {
Log.d(TAG, "Ad did present")
}
// Optional: Before closing full screen
override fun onAdWillDismissFullScreen(ad: AdropRewardedAd) {
Log.d(TAG, "Ad will dismiss")
}
// Optional: After closing full screen
override fun onAdDidDismissFullScreen(ad: AdropRewardedAd) {
Log.d(TAG, "Ad dismissed")
// Resume background music, etc.
// Preload next ad
rewardedAd?.load()
}
// Optional: Failed to show
override fun onAdFailedToShowFullScreen(ad: AdropRewardedAd, errorCode: AdropErrorCode) {
Log.d(TAG, "Ad failed to show: $errorCode")
}
}
// 3. Load ad
rewardedAd?.load()
// Handle watch ad button click
findViewById(R.id.btnShowRewardedAd).setOnClickListener {
showRewardedAd()
}
}
private fun showRewardedAd() {
// Check if ad is loaded
if (rewardedAd?.isLoaded != true) {
Log.d(TAG, "Ad is not loaded yet")
return
}
// 4. Show ad and handle reward
rewardedAd?.show(this) { type, amount ->
// Process reward
Log.d(TAG, "Reward earned - type: $type, amount: $amount")
grantReward(type, amount)
}
}
private fun grantReward(type: Int, amount: Int) {
// Grant reward to user
// e.g., Add in-game currency, restore lives, etc.
}
override fun onDestroy() {
super.onDestroy()
rewardedAd?.destroy()
rewardedAd = null
}
companion object {
private const val TAG = "RewardedActivity"
}
}
```
### Java Example
```java theme={null}
import io.adrop.ads.rewardedAd.AdropRewardedAd;
import io.adrop.ads.rewardedAd.AdropRewardedAdListener;
import io.adrop.ads.model.AdropErrorCode;
public class RewardedActivity extends AppCompatActivity {
private static final String TAG = "RewardedActivity";
private AdropRewardedAd rewardedAd;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_rewarded);
// 1. Create AdropRewardedAd instance
rewardedAd = new AdropRewardedAd(this, "YOUR_UNIT_ID");
// 2. Set Listener
rewardedAd.setRewardedAdListener(new AdropRewardedAdListener() {
// Required: Ad received successfully
@Override
public void onAdReceived(@NonNull AdropRewardedAd ad) {
Log.d(TAG, "Ad received");
// Enable watch ad button, etc.
}
// Required: Ad failed to receive
@Override
public void onAdFailedToReceive(@NonNull AdropRewardedAd ad, @NonNull AdropErrorCode errorCode) {
Log.d(TAG, "Ad failed to receive: " + errorCode);
// Keep watch ad button disabled, etc.
}
// Optional: Ad impression
@Override
public void onAdImpression(@NonNull AdropRewardedAd ad) {
Log.d(TAG, "Ad impression");
}
// Optional: Ad clicked
@Override
public void onAdClicked(@NonNull AdropRewardedAd ad) {
Log.d(TAG, "Ad clicked");
}
// Optional: Before showing full screen
@Override
public void onAdWillPresentFullScreen(@NonNull AdropRewardedAd ad) {
Log.d(TAG, "Ad will present");
// Pause background music, etc.
}
// Optional: After showing full screen
@Override
public void onAdDidPresentFullScreen(@NonNull AdropRewardedAd ad) {
Log.d(TAG, "Ad did present");
}
// Optional: Before closing full screen
@Override
public void onAdWillDismissFullScreen(@NonNull AdropRewardedAd ad) {
Log.d(TAG, "Ad will dismiss");
}
// Optional: After closing full screen
@Override
public void onAdDidDismissFullScreen(@NonNull AdropRewardedAd ad) {
Log.d(TAG, "Ad dismissed");
// Resume background music, etc.
// Preload next ad
rewardedAd.load();
}
// Optional: Failed to show
@Override
public void onAdFailedToShowFullScreen(@NonNull AdropRewardedAd ad, @NonNull AdropErrorCode errorCode) {
Log.d(TAG, "Ad failed to show: " + errorCode);
}
});
// 3. Load ad
rewardedAd.load();
// Handle watch ad button click
findViewById(R.id.btnShowRewardedAd).setOnClickListener(v -> showRewardedAd());
}
private void showRewardedAd() {
// Check if ad is loaded
if (!rewardedAd.isLoaded()) {
Log.d(TAG, "Ad is not loaded yet");
return;
}
// 4. Show ad and handle reward
rewardedAd.show(this, (type, amount) -> {
// Process reward
Log.d(TAG, "Reward earned - type: " + type + ", amount: " + amount);
grantReward(type, amount);
});
}
private void grantReward(int type, int amount) {
// Grant reward to user
// e.g., Add in-game currency, restore lives, etc.
}
@Override
protected void onDestroy() {
super.onDestroy();
if (rewardedAd != null) {
rewardedAd.destroy();
rewardedAd = null;
}
}
}
```
***
## AdropRewardedAd Class
### Constructor
```kotlin theme={null}
AdropRewardedAd(context: Context, unitId: String)
```
Activity or Application Context
Ad unit ID issued from the Adrop console
### Properties
Ad unit ID (read-only)
Whether the ad is loaded and ready to show (read-only)
Creative ID of the loaded ad (read-only)
Transaction ID of the ad (read-only)
Campaign ID of the ad (read-only)
Whether this is a backfill ad (read-only)
Listener for receiving ad events
### Methods
Request an ad. Results are delivered via `onAdReceived` or `onAdFailedToReceive` callbacks.
Show the ad.
* `activity`: Activity to display the ad
* `userDidEarnRewardHandler`: Reward callback `(type: Int, amount: Int) -> Unit`
Release ad resources. Must be called in Activity's `onDestroy()`.
***
## AdropRewardedAdListener Interface
### Required Methods
Called when ad is successfully received. You can show the ad with `show()` from this point.
Called when ad fails to load. Check the error code for the failure reason.
### Optional Methods
Called when ad impression is recorded.
Called when user clicks the ad.
Called just before the ad screen is displayed. Good time to pause background music.
Called after the ad screen is fully displayed.
Called just before the ad screen closes.
Called after the ad screen is fully closed. Good time to resume background music or preload next ad.
Called when ad fails to show. Check the error code for the failure reason.
***
## Reward Handling
### Reward Callback
Pass a reward callback as the `userDidEarnRewardHandler` parameter to the `show()` method.
```kotlin Kotlin theme={null}
rewardedAd?.show(this) { type, amount ->
// type: Reward type (Int)
// amount: Reward amount (Int)
Log.d(TAG, "Reward earned - type: $type, amount: $amount")
grantReward(type, amount)
}
```
```java Java theme={null}
rewardedAd.show(this, (type, amount) -> {
// type: Reward type (Int)
// amount: Reward amount (Int)
Log.d(TAG, "Reward earned - type: " + type + ", amount: " + amount);
grantReward(type, amount);
});
```
### Reward Type and Amount
* **type**: Reward type configured in Adrop console (integer value)
* **amount**: Reward amount configured in Adrop console (integer value)
## Best Practices
### 1. Preload Ads
Preload ads for better user experience.
```kotlin Kotlin theme={null}
class GameActivity : AppCompatActivity() {
private var rewardedAd: AdropRewardedAd? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_game)
// Preload ad on screen entry
loadRewardedAd()
}
private fun loadRewardedAd() {
rewardedAd = AdropRewardedAd(this, "YOUR_UNIT_ID")
rewardedAd?.rewardedAdListener = object : AdropRewardedAdListener {
override fun onAdReceived(ad: AdropRewardedAd) {
Log.d(TAG, "Ad ready")
}
override fun onAdFailedToReceive(ad: AdropRewardedAd, errorCode: AdropErrorCode) {
Log.d(TAG, "Ad load failed: $errorCode")
}
override fun onAdDidDismissFullScreen(ad: AdropRewardedAd) {
// Preload next ad after closing
loadRewardedAd()
}
}
rewardedAd?.load()
}
private fun showRewardedAd() {
rewardedAd?.show(this) { type, amount ->
grantReward(type, amount)
}
}
}
```
```java Java theme={null}
public class GameActivity extends AppCompatActivity {
private AdropRewardedAd rewardedAd;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_game);
// Preload ad on screen entry
loadRewardedAd();
}
private void loadRewardedAd() {
rewardedAd = new AdropRewardedAd(this, "YOUR_UNIT_ID");
rewardedAd.setRewardedAdListener(new AdropRewardedAdListener() {
@Override
public void onAdReceived(@NonNull AdropRewardedAd ad) {
Log.d(TAG, "Ad ready");
}
@Override
public void onAdFailedToReceive(@NonNull AdropRewardedAd ad, @NonNull AdropErrorCode errorCode) {
Log.d(TAG, "Ad load failed: " + errorCode);
}
@Override
public void onAdDidDismissFullScreen(@NonNull AdropRewardedAd ad) {
// Preload next ad after closing
loadRewardedAd();
}
});
rewardedAd.load();
}
private void showRewardedAd() {
rewardedAd.show(this, (type, amount) -> {
grantReward(type, amount);
});
}
}
```
### 2. Check Ad Ready State
Check if ad is loaded and update UI accordingly.
```kotlin Kotlin theme={null}
class GameActivity : AppCompatActivity() {
private var rewardedAd: AdropRewardedAd? = null
private var isAdReady = false
private fun updateButtonState() {
findViewById(R.id.btnWatchAd).apply {
isEnabled = isAdReady
alpha = if (isAdReady) 1.0f else 0.5f
text = if (isAdReady) "Watch Video for Lives" else "Loading Ad..."
}
}
private val adListener = object : AdropRewardedAdListener {
override fun onAdReceived(ad: AdropRewardedAd) {
isAdReady = true
updateButtonState()
}
override fun onAdFailedToReceive(ad: AdropRewardedAd, errorCode: AdropErrorCode) {
isAdReady = false
updateButtonState()
}
override fun onAdDidDismissFullScreen(ad: AdropRewardedAd) {
isAdReady = false
updateButtonState()
// Load next ad
rewardedAd?.load()
}
}
}
```
```java Java theme={null}
public class GameActivity extends AppCompatActivity {
private AdropRewardedAd rewardedAd;
private boolean isAdReady = false;
private void updateButtonState() {
Button btnWatchAd = findViewById(R.id.btnWatchAd);
btnWatchAd.setEnabled(isAdReady);
btnWatchAd.setAlpha(isAdReady ? 1.0f : 0.5f);
btnWatchAd.setText(isAdReady ? "Watch Video for Lives" : "Loading Ad...");
}
private final AdropRewardedAdListener adListener = new AdropRewardedAdListener() {
@Override
public void onAdReceived(@NonNull AdropRewardedAd ad) {
isAdReady = true;
updateButtonState();
}
@Override
public void onAdFailedToReceive(@NonNull AdropRewardedAd ad, @NonNull AdropErrorCode errorCode) {
isAdReady = false;
updateButtonState();
}
@Override
public void onAdDidDismissFullScreen(@NonNull AdropRewardedAd ad) {
isAdReady = false;
updateButtonState();
// Load next ad
rewardedAd.load();
}
};
}
```
### 3. Background Music Handling
Control background music appropriately before and after ad display.
```kotlin Kotlin theme={null}
private val adListener = object : AdropRewardedAdListener {
override fun onAdReceived(ad: AdropRewardedAd) {
// Required implementation
}
override fun onAdFailedToReceive(ad: AdropRewardedAd, errorCode: AdropErrorCode) {
// Required implementation
}
override fun onAdWillPresentFullScreen(ad: AdropRewardedAd) {
// Pause background music before showing ad
AudioManager.pauseBackgroundMusic()
}
override fun onAdDidDismissFullScreen(ad: AdropRewardedAd) {
// Resume background music after ad closes
AudioManager.resumeBackgroundMusic()
}
}
```
```java Java theme={null}
private final AdropRewardedAdListener adListener = new AdropRewardedAdListener() {
@Override
public void onAdReceived(@NonNull AdropRewardedAd ad) {
// Required implementation
}
@Override
public void onAdFailedToReceive(@NonNull AdropRewardedAd ad, @NonNull AdropErrorCode errorCode) {
// Required implementation
}
@Override
public void onAdWillPresentFullScreen(@NonNull AdropRewardedAd ad) {
// Pause background music before showing ad
AudioManager.pauseBackgroundMusic();
}
@Override
public void onAdDidDismissFullScreen(@NonNull AdropRewardedAd ad) {
// Resume background music after ad closes
AudioManager.resumeBackgroundMusic();
}
};
```
### 4. Error Handling
Implement retry logic when ad loading fails.
```kotlin Kotlin theme={null}
private val adListener = object : AdropRewardedAdListener {
override fun onAdReceived(ad: AdropRewardedAd) {
Log.d(TAG, "Ad load success")
}
override fun onAdFailedToReceive(ad: AdropRewardedAd, errorCode: AdropErrorCode) {
Log.d(TAG, "Ad load failed: $errorCode")
// Retry on network error
if (errorCode == AdropErrorCode.ERROR_CODE_NETWORK) {
Handler(Looper.getMainLooper()).postDelayed({
rewardedAd?.load()
}, 3000)
}
}
override fun onAdFailedToShowFullScreen(ad: AdropRewardedAd, errorCode: AdropErrorCode) {
Log.d(TAG, "Ad show failed: $errorCode")
// Notify user
Toast.makeText(
this@GameActivity,
"Cannot show ad. Please try again later.",
Toast.LENGTH_SHORT
).show()
}
}
```
```java Java theme={null}
private final AdropRewardedAdListener adListener = new AdropRewardedAdListener() {
@Override
public void onAdReceived(@NonNull AdropRewardedAd ad) {
Log.d(TAG, "Ad load success");
}
@Override
public void onAdFailedToReceive(@NonNull AdropRewardedAd ad, @NonNull AdropErrorCode errorCode) {
Log.d(TAG, "Ad load failed: " + errorCode);
// Retry on network error
if (errorCode == AdropErrorCode.ERROR_CODE_NETWORK) {
new Handler(Looper.getMainLooper()).postDelayed(() -> {
rewardedAd.load();
}, 3000);
}
}
@Override
public void onAdFailedToShowFullScreen(@NonNull AdropRewardedAd ad, @NonNull AdropErrorCode errorCode) {
Log.d(TAG, "Ad show failed: " + errorCode);
// Notify user
Toast.makeText(
GameActivity.this,
"Cannot show ad. Please try again later.",
Toast.LENGTH_SHORT
).show();
}
};
```
### 5. Lifecycle Management
Clean up ad resources when Activity is destroyed.
```kotlin Kotlin theme={null}
class GameActivity : AppCompatActivity() {
private var rewardedAd: AdropRewardedAd? = null
override fun onDestroy() {
super.onDestroy()
rewardedAd?.destroy()
rewardedAd = null
}
}
```
```java Java theme={null}
public class GameActivity extends AppCompatActivity {
private AdropRewardedAd rewardedAd;
@Override
protected void onDestroy() {
super.onDestroy();
if (rewardedAd != null) {
rewardedAd.destroy();
rewardedAd = null;
}
}
}
```
***
## Complete Example
```kotlin Kotlin theme={null}
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import io.adrop.ads.rewardedAd.AdropRewardedAd
import io.adrop.ads.rewardedAd.AdropRewardedAdListener
import io.adrop.ads.model.AdropErrorCode
class GameActivity : AppCompatActivity() {
private var rewardedAd: AdropRewardedAd? = null
private var isAdReady = false
private lateinit var btnWatchAd: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_game)
btnWatchAd = findViewById(R.id.btnWatchAd)
btnWatchAd.setOnClickListener {
showRewardedAd()
}
// Preload ad
loadRewardedAd()
}
private fun loadRewardedAd() {
rewardedAd = AdropRewardedAd(this, "PUBLIC_TEST_UNIT_ID_REWARDED")
rewardedAd?.rewardedAdListener = object : AdropRewardedAdListener {
override fun onAdReceived(ad: AdropRewardedAd) {
Log.d(TAG, "Ad received")
isAdReady = true
updateButtonState()
}
override fun onAdFailedToReceive(ad: AdropRewardedAd, errorCode: AdropErrorCode) {
Log.d(TAG, "Ad failed to receive: $errorCode")
isAdReady = false
updateButtonState()
// Retry after 3 seconds on network error
if (errorCode == AdropErrorCode.ERROR_CODE_NETWORK) {
Handler(Looper.getMainLooper()).postDelayed({
rewardedAd?.load()
}, 3000)
}
}
override fun onAdImpression(ad: AdropRewardedAd) {
Log.d(TAG, "Ad impression")
}
override fun onAdClicked(ad: AdropRewardedAd) {
Log.d(TAG, "Ad clicked")
}
override fun onAdWillPresentFullScreen(ad: AdropRewardedAd) {
Log.d(TAG, "Ad will present")
// Pause background music
pauseBackgroundMusic()
}
override fun onAdDidPresentFullScreen(ad: AdropRewardedAd) {
Log.d(TAG, "Ad did present")
}
override fun onAdWillDismissFullScreen(ad: AdropRewardedAd) {
Log.d(TAG, "Ad will dismiss")
}
override fun onAdDidDismissFullScreen(ad: AdropRewardedAd) {
Log.d(TAG, "Ad dismissed")
isAdReady = false
updateButtonState()
// Resume background music
resumeBackgroundMusic()
// Preload next ad
loadRewardedAd()
}
override fun onAdFailedToShowFullScreen(ad: AdropRewardedAd, errorCode: AdropErrorCode) {
Log.d(TAG, "Ad failed to show: $errorCode")
Toast.makeText(
this@GameActivity,
"Cannot show ad. Please try again later.",
Toast.LENGTH_SHORT
).show()
}
}
rewardedAd?.load()
}
private fun showRewardedAd() {
if (!isAdReady) {
Toast.makeText(this, "Ad is not loaded yet", Toast.LENGTH_SHORT).show()
return
}
rewardedAd?.show(this) { type, amount ->
Log.d(TAG, "Reward earned - type: $type, amount: $amount")
grantReward(type, amount)
}
}
private fun grantReward(type: Int, amount: Int) {
// Grant reward to user
runOnUiThread {
Toast.makeText(
this,
"Reward earned! Type: $type, Amount: $amount",
Toast.LENGTH_LONG
).show()
}
// Implement actual reward logic
// e.g., Add in-game currency, restore lives, etc.
}
private fun updateButtonState() {
btnWatchAd.isEnabled = isAdReady
btnWatchAd.alpha = if (isAdReady) 1.0f else 0.5f
btnWatchAd.text = if (isAdReady) "Watch Video for Lives" else "Loading Ad..."
}
private fun pauseBackgroundMusic() {
// Pause background music logic
}
private fun resumeBackgroundMusic() {
// Resume background music logic
}
override fun onDestroy() {
super.onDestroy()
rewardedAd?.destroy()
rewardedAd = null
}
companion object {
private const val TAG = "GameActivity"
}
}
```
```java Java theme={null}
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.widget.Button;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import io.adrop.ads.rewardedAd.AdropRewardedAd;
import io.adrop.ads.rewardedAd.AdropRewardedAdListener;
import io.adrop.ads.model.AdropErrorCode;
public class GameActivity extends AppCompatActivity {
private static final String TAG = "GameActivity";
private AdropRewardedAd rewardedAd;
private boolean isAdReady = false;
private Button btnWatchAd;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_game);
btnWatchAd = findViewById(R.id.btnWatchAd);
btnWatchAd.setOnClickListener(v -> showRewardedAd());
// Preload ad
loadRewardedAd();
}
private void loadRewardedAd() {
rewardedAd = new AdropRewardedAd(this, "PUBLIC_TEST_UNIT_ID_REWARDED");
rewardedAd.setRewardedAdListener(new AdropRewardedAdListener() {
@Override
public void onAdReceived(@NonNull AdropRewardedAd ad) {
Log.d(TAG, "Ad received");
isAdReady = true;
updateButtonState();
}
@Override
public void onAdFailedToReceive(@NonNull AdropRewardedAd ad, @NonNull AdropErrorCode errorCode) {
Log.d(TAG, "Ad failed to receive: " + errorCode);
isAdReady = false;
updateButtonState();
// Retry after 3 seconds on network error
if (errorCode == AdropErrorCode.ERROR_CODE_NETWORK) {
new Handler(Looper.getMainLooper()).postDelayed(() -> {
rewardedAd.load();
}, 3000);
}
}
@Override
public void onAdImpression(@NonNull AdropRewardedAd ad) {
Log.d(TAG, "Ad impression");
}
@Override
public void onAdClicked(@NonNull AdropRewardedAd ad) {
Log.d(TAG, "Ad clicked");
}
@Override
public void onAdWillPresentFullScreen(@NonNull AdropRewardedAd ad) {
Log.d(TAG, "Ad will present");
// Pause background music
pauseBackgroundMusic();
}
@Override
public void onAdDidPresentFullScreen(@NonNull AdropRewardedAd ad) {
Log.d(TAG, "Ad did present");
}
@Override
public void onAdWillDismissFullScreen(@NonNull AdropRewardedAd ad) {
Log.d(TAG, "Ad will dismiss");
}
@Override
public void onAdDidDismissFullScreen(@NonNull AdropRewardedAd ad) {
Log.d(TAG, "Ad dismissed");
isAdReady = false;
updateButtonState();
// Resume background music
resumeBackgroundMusic();
// Preload next ad
loadRewardedAd();
}
@Override
public void onAdFailedToShowFullScreen(@NonNull AdropRewardedAd ad, @NonNull AdropErrorCode errorCode) {
Log.d(TAG, "Ad failed to show: " + errorCode);
Toast.makeText(
GameActivity.this,
"Cannot show ad. Please try again later.",
Toast.LENGTH_SHORT
).show();
}
});
rewardedAd.load();
}
private void showRewardedAd() {
if (!isAdReady) {
Toast.makeText(this, "Ad is not loaded yet", Toast.LENGTH_SHORT).show();
return;
}
rewardedAd.show(this, (type, amount) -> {
Log.d(TAG, "Reward earned - type: " + type + ", amount: " + amount);
grantReward(type, amount);
});
}
private void grantReward(int type, int amount) {
// Grant reward to user
runOnUiThread(() -> {
Toast.makeText(
this,
"Reward earned! Type: " + type + ", Amount: " + amount,
Toast.LENGTH_LONG
).show();
});
// Implement actual reward logic
// e.g., Add in-game currency, restore lives, etc.
}
private void updateButtonState() {
btnWatchAd.setEnabled(isAdReady);
btnWatchAd.setAlpha(isAdReady ? 1.0f : 0.5f);
btnWatchAd.setText(isAdReady ? "Watch Video for Lives" : "Loading Ad...");
}
private void pauseBackgroundMusic() {
// Pause background music logic
}
private void resumeBackgroundMusic() {
// Resume background music logic
}
@Override
protected void onDestroy() {
super.onDestroy();
if (rewardedAd != null) {
rewardedAd.destroy();
rewardedAd = null;
}
}
}
```
***
## Testing
### Test Unit ID
Always use test unit IDs during development.
```kotlin Kotlin theme={null}
// Development environment
val rewardedAd = AdropRewardedAd(this, "PUBLIC_TEST_UNIT_ID_REWARDED")
// Production environment
val rewardedAd = AdropRewardedAd(this, "YOUR_PRODUCTION_UNIT_ID")
```
```java Java theme={null}
// Development environment
AdropRewardedAd rewardedAd = new AdropRewardedAd(this, "PUBLIC_TEST_UNIT_ID_REWARDED");
// Production environment
AdropRewardedAd rewardedAd = new AdropRewardedAd(this, "YOUR_PRODUCTION_UNIT_ID");
```
Testing with actual unit IDs may be considered invalid traffic and could result in account suspension. Always use test unit IDs.
### Environment-based Unit ID Management
Automatically switch unit IDs based on build type.
```kotlin Kotlin theme={null}
object AdUnitID {
val REWARDED: String
get() = if (BuildConfig.DEBUG) {
"PUBLIC_TEST_UNIT_ID_REWARDED"
} else {
"YOUR_PRODUCTION_UNIT_ID"
}
}
// Usage
val rewardedAd = AdropRewardedAd(this, AdUnitID.REWARDED)
```
```java Java theme={null}
public class AdUnitID {
public static String getREWARDED() {
if (BuildConfig.DEBUG) {
return "PUBLIC_TEST_UNIT_ID_REWARDED";
} else {
return "YOUR_PRODUCTION_UNIT_ID";
}
}
}
// Usage
AdropRewardedAd rewardedAd = new AdropRewardedAd(this, AdUnitID.getREWARDED());
```
***
## Backfill Ads
When backfill ads are enabled, backfill ads are automatically loaded when direct ads are unavailable. Use the `isBackfilled` property to check if the ad is a backfill ad.
```kotlin Kotlin theme={null}
rewardedAd.rewardedAdListener = object : AdropRewardedAdListener {
override fun onAdReceived(ad: AdropRewardedAd) {
if (ad.isBackfilled) {
println("Backfill ad loaded")
} else {
println("Direct ad loaded")
}
}
override fun onAdFailedToReceive(ad: AdropRewardedAd, errorCode: AdropErrorCode) {
when (errorCode) {
AdropErrorCode.ERROR_CODE_AD_NO_FILL -> {
println("No direct ad, requesting backfill...")
}
AdropErrorCode.ERROR_CODE_AD_BACKFILL_NO_FILL -> {
println("Backfill ad also not available")
}
else -> {
println("Ad load failed: $errorCode")
}
}
}
}
```
```java Java theme={null}
rewardedAd.setRewardedAdListener(new AdropRewardedAdListener() {
@Override
public void onAdReceived(AdropRewardedAd ad) {
if (ad.isBackfilled()) {
System.out.println("Backfill ad loaded");
} else {
System.out.println("Direct ad loaded");
}
}
@Override
public void onAdFailedToReceive(AdropRewardedAd ad, AdropErrorCode errorCode) {
if (errorCode == AdropErrorCode.ERROR_CODE_AD_NO_FILL) {
System.out.println("No direct ad, requesting backfill...");
} else if (errorCode == AdropErrorCode.ERROR_CODE_AD_BACKFILL_NO_FILL) {
System.out.println("Backfill ad also not available");
} else {
System.out.println("Ad load failed: " + errorCode);
}
}
});
```
To use backfill ads, add the `io.adrop:adrop-ads-backfill` dependency. See [Getting Started](/sdk/android/overview).
***
## Server-Side Verification
Server-side verification (SSV) allows you to verify reward grants on your server, preventing client-side manipulation. Use `ServerSideVerificationOptions` to pass user identification and custom data that will be included in the server-to-server callback.
To receive SSV callbacks on your server, register a callback URL in the console: **\[Settings] > \[Integrations] > Reward Ad SSV**. The `userId` and `customData` set here are included in the encrypted payload sent to that URL. See the [Reward Ad SSV guide](/integrations/ssv) for payload decryption and setup.
### Setting Up SSV
```kotlin Kotlin theme={null}
val rewardedAd = AdropRewardedAd(this, "YOUR_UNIT_ID")
// Set server-side verification options before loading the ad
rewardedAd.serverSideVerificationOptions = ServerSideVerificationOptions(
userId = "user_12345",
customData = "extra_info"
)
rewardedAd.rewardedAdListener = object : AdropRewardedAdListener {
override fun onAdReceived(ad: AdropRewardedAd) {
ad.show(this@MainActivity) { type, amount ->
Log.d("Reward", "Type: $type, Amount: $amount")
}
}
override fun onAdFailedToReceive(ad: AdropRewardedAd, errorCode: AdropErrorCode) {
// Handle error
}
}
rewardedAd.load()
```
```java Java theme={null}
AdropRewardedAd rewardedAd = new AdropRewardedAd(this, "YOUR_UNIT_ID");
// Set server-side verification options before loading the ad
rewardedAd.setServerSideVerificationOptions(
new ServerSideVerificationOptions("user_12345", "extra_info")
);
rewardedAd.setRewardedAdListener(new AdropRewardedAdListener() {
@Override
public void onAdReceived(AdropRewardedAd ad) {
ad.show(this, (type, amount) -> {
Log.d("Reward", "Type: " + type + ", Amount: " + amount);
});
}
});
rewardedAd.load();
```
### ServerSideVerificationOptions
| Property | Type | Description |
| - | - | - |
| `userId` | `String?` | User identifier for server-side verification |
| `customData` | `String?` | Custom data string to include in the callback |
When the ad falls back to backfill, `customData` is sanitized to base64url before being forwarded (`+`→`-`, `/`→`_`, `=` stripped; backfill 1.12.1+). If you pack a base64 payload into `customData`, decode it as base64url on your server.
Set `serverSideVerificationOptions` before calling `load()`. The `userId` and `customData` values are sent to your server callback URL when the user earns a reward.
***
## Related Documentation
Interstitial ad implementation guide
AdropErrorCode reference
View complete example code
# Splash Ads
Source: https://docs.adrop.io/sdk/android/splash
Learn how to implement splash ads in your Android app.
## Overview
Splash Ads are advertisements displayed on the splash screen when the app launches. The ad is shown alongside your app's logo, providing a natural user experience.
### Features
* Natural ad exposure at app launch
* Maintains brand image with app logo
* 360dp × 270dp ad area, 288dp × 288dp logo area
* Image ad support
* Android 12+ system splash screen support
Use the test unit ID during development: `PUBLIC_TEST_UNIT_ID_SPLASH`
***
## Caching Behavior
Splash ads are prefetched and cached on the device so the creative can appear instantly at launch, before any network request completes. On the next launch the cached creative is shown first, and a fresh ad request runs in the background to refresh the cache for subsequent launches.
Because of this prefetch model, a creative that is **paused** in Ad Control Console may still be shown from the device cache until the next refresh cycle completes on each user's device. Plan creative pauses and replacements with a buffer for the cache to roll over across the install base.
***
## Ad and Logo Sizes
Splash ads use predefined areas:
* **Logo area**: 288dp × 288dp (center of screen)
* **Ad area**: 360dp × 270dp (bottom of screen)
* The ad is displayed at the bottom of the screen, with the app logo positioned at the top
***
## Implementation Methods
Splash ads can be implemented in two ways depending on your app's requirements:
1. **Using AdropSplashAd** - Integrates with system splash screen (Android 12+)
2. **Using AdropSplashAdView** - Custom splash screen configuration
***
## Method 1: Using AdropSplashAd (System Splash Integration)
This method integrates with the system splash screen on Android 12 (API 31) and above. `AdropSplashAdActivity` automatically manages the splash screen.
### Step 1: Configure strings.xml
Add the following settings to `app/src/main/res/values/strings.xml`.
```xml title="res/values/strings.xml" theme={null}
My AppYOUR_SPLASH_UNIT_IDcom.yourcompany.yourapp.MainActivity
```
Use `PUBLIC_TEST_UNIT_ID_SPLASH` instead of `YOUR_SPLASH_UNIT_ID` for testing.
### Step 2: Add Splash Logo Image
Add your app logo to the `res/drawable/` folder.
```
app/src/main/res/drawable/splash_logo.png
```
Recommended size: 288dp × 288dp (@2x: 576px, @3x: 864px)
### Step 3: Configure AndroidManifest.xml
Set `AdropSplashAdActivity` as the launcher activity.
```xml title="AndroidManifest.xml" theme={null}
```
`android:exported="true"` is required for `AdropSplashAdActivity`.
### Step 4: Configure Splash Theme
Add the splash theme to `res/values/themes.xml`.
```xml title="res/values/themes.xml" theme={null}
```
On devices below Android 12, the system splash is not displayed and `AdropSplashAdActivity` is shown directly.
### Step 5: Customize Layout (Optional)
If you want to use a custom layout instead of the default, create `activity_adrop_splash_ad.xml` in your app's `res/layout/` folder.
```xml title="res/layout/activity_adrop_splash_ad.xml" theme={null}
```
An `ImageView` with `android:id="@+id/adrop_splash_ad_image"` is required. The ad will be displayed in this view.
### Step 6: Initialize SDK in Application
```kotlin title="MyApplication.kt" theme={null}
import android.app.Application
import io.adrop.ads.Adrop
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// Initialize SDK
Adrop.initialize(this, production = false)
}
}
```
### Step 7: Implement AdropSplashAd Listener (Optional)
To receive splash ad events, set up a listener in your `Application` class.
```kotlin Kotlin theme={null}
import android.app.Application
import io.adrop.ads.Adrop
import io.adrop.ads.splash.AdropSplashAd
import io.adrop.ads.splash.AdropSplashAdListener
import io.adrop.ads.model.AdropErrorCode
class MyApplication : Application() {
private lateinit var splashAd: AdropSplashAd
override fun onCreate() {
super.onCreate()
// Initialize SDK
Adrop.initialize(this, production = false)
// Configure splash ad
splashAd = AdropSplashAd(this) { splashAd ->
// shouldSkip: Determines whether to skip splash ad
// Return true to skip the ad and go directly to the main screen
checkShouldSkipSplash()
}
splashAd.splashAdListener = object : AdropSplashAdListener {
override fun onAdReceived(ad: AdropSplashAd) {
println("Splash ad received successfully")
}
override fun onAdFailedToReceive(ad: AdropSplashAd, errorCode: AdropErrorCode) {
println("Failed to receive splash ad: $errorCode")
}
override fun onAdImpression(ad: AdropSplashAd) {
println("Splash ad impression")
}
override fun onAdClose(ad: AdropSplashAd, impressed: Boolean) {
println("Splash ad closed - impressed: $impressed")
}
}
}
private fun checkShouldSkipSplash(): Boolean {
// Example: Skip splash when entering via deep link or under certain conditions
// Such as when the app is launched via push notification
return false
}
}
```
```java Java theme={null}
import android.app.Application;
import io.adrop.ads.Adrop;
import io.adrop.ads.splash.AdropSplashAd;
import io.adrop.ads.splash.AdropSplashAdListener;
import io.adrop.ads.model.AdropErrorCode;
public class MyApplication extends Application {
private AdropSplashAd splashAd;
@Override
public void onCreate() {
super.onCreate();
// Initialize SDK
Adrop.INSTANCE.initialize(this, false, new String[]{});
// Configure splash ad
splashAd = new AdropSplashAd(this, ad -> checkShouldSkipSplash());
splashAd.setSplashAdListener(new AdropSplashAdListener() {
@Override
public void onAdReceived(AdropSplashAd ad) {
System.out.println("Splash ad received successfully");
}
@Override
public void onAdFailedToReceive(AdropSplashAd ad, AdropErrorCode errorCode) {
System.out.println("Failed to receive splash ad: " + errorCode);
}
@Override
public void onAdImpression(AdropSplashAd ad) {
System.out.println("Splash ad impression");
}
@Override
public void onAdClose(AdropSplashAd ad, boolean impressed) {
System.out.println("Splash ad closed - impressed: " + impressed);
}
});
}
private boolean checkShouldSkipSplash() {
// Determine whether to skip splash ad
return false;
}
}
```
### AdropSplashAd Properties
| Property | Type | Description |
| - | - | - |
| `unitId` | `String` | Ad unit ID (read-only) |
| `creativeId` | `String` | Ad creative ID (read-only) |
| `isClosed` | `Boolean` | Whether splash ad is closed (read-only) |
| `isDestroyed` | `Boolean` | Whether splash ad is destroyed (read-only) |
### AdropSplashAd Methods
| Method | Description |
| - | - |
| `close()` | Immediately closes splash ad and navigates to next screen |
| `destroy()` | Releases resources and unregisters lifecycle callbacks. Call when splash ad is no longer needed. |
### shouldSkip Callback
Use the `shouldSkip` callback in the `AdropSplashAd` constructor to skip splash ads under certain conditions.
```kotlin theme={null}
val splashAd = AdropSplashAd(application) { splashAd ->
// Skip splash when app is launched via push notification
val isPushNotification = intent?.extras?.containsKey("push_notification") == true
// Skip splash when entering via deep link
val hasDeepLink = intent?.data != null
isPushNotification || hasDeepLink
}
```
When `shouldSkip` returns `true`, the splash ad is immediately closed and navigates to the next screen.
### Configure Splash Display Duration (Optional)
To change the default display duration (2 seconds), create `res/values/integers.xml` file and configure it.
```xml title="res/values/integers.xml" theme={null}
10002000
```
The configurable range is 500ms to 3000ms. Values outside this range are automatically corrected.
### Skip Splash on Push Notification Deep Link (Optional)
You can configure to skip splash ads when the app is launched via push notification with a deep link.
```xml title="res/values/bools.xml" theme={null}
true
```
***
## Method 2: Using AdropSplashAdView (Custom Splash)
Use this when you want to configure a custom splash screen yourself. Create your own splash Activity and place `AdropSplashAdView`.
### Step 1: Create Layout
```xml title="activity_splash.xml" theme={null}
```
### Step 2: Implement SplashActivity
```kotlin Kotlin theme={null}
import android.content.Intent
import android.os.Bundle
import android.widget.FrameLayout
import androidx.appcompat.app.AppCompatActivity
import io.adrop.ads.splash.AdropSplashAdView
import io.adrop.ads.splash.AdropSplashAdViewListener
import io.adrop.ads.model.AdropErrorCode
class SplashActivity : AppCompatActivity() {
private lateinit var splashAdView: AdropSplashAdView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_splash)
// Initialize splash ad view
splashAdView = AdropSplashAdView(
context = this,
unitId = "YOUR_SPLASH_UNIT_ID",
adRequestTimeout = 1000L // Ad request timeout (milliseconds)
)
// Set listener
splashAdView.listener = object : AdropSplashAdViewListener {
override fun onAdReceived(ad: AdropSplashAdView) {
println("Splash ad received successfully")
}
override fun onAdFailedToReceive(ad: AdropSplashAdView, errorCode: AdropErrorCode) {
println("Failed to receive splash ad: $errorCode")
}
override fun onAdImpression(ad: AdropSplashAdView) {
println("Splash ad impression")
}
override fun onAdClose(ad: AdropSplashAdView, impressed: Boolean) {
println("Splash ad closed - impressed: $impressed")
goToMainActivity()
}
}
// Load and display ad
findViewById(R.id.splash_ad_container).addView(splashAdView)
splashAdView.load()
}
private fun goToMainActivity() {
startActivity(Intent(this, MainActivity::class.java))
finish()
}
}
```
```java Java theme={null}
import android.content.Intent;
import android.os.Bundle;
import android.widget.FrameLayout;
import androidx.appcompat.app.AppCompatActivity;
import io.adrop.ads.splash.AdropSplashAdView;
import io.adrop.ads.splash.AdropSplashAdViewListener;
import io.adrop.ads.model.AdropErrorCode;
public class SplashActivity extends AppCompatActivity {
private AdropSplashAdView splashAdView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
// Initialize splash ad view
splashAdView = new AdropSplashAdView(
this,
"YOUR_SPLASH_UNIT_ID",
1000L // Ad request timeout (milliseconds)
);
// Set listener
splashAdView.setListener(new AdropSplashAdViewListener() {
@Override
public void onAdReceived(AdropSplashAdView ad) {
System.out.println("Splash ad received successfully");
}
@Override
public void onAdFailedToReceive(AdropSplashAdView ad, AdropErrorCode errorCode) {
System.out.println("Failed to receive splash ad: " + errorCode);
}
@Override
public void onAdImpression(AdropSplashAdView ad) {
System.out.println("Splash ad impression");
}
@Override
public void onAdClose(AdropSplashAdView ad, boolean impressed) {
System.out.println("Splash ad closed - impressed: " + impressed);
goToMainActivity();
}
});
// Load and display ad
FrameLayout container = findViewById(R.id.splash_ad_container);
container.addView(splashAdView);
splashAdView.load();
}
private void goToMainActivity() {
startActivity(new Intent(this, MainActivity.class));
finish();
}
}
```
### Step 3: Configure AndroidManifest.xml
```xml title="AndroidManifest.xml" theme={null}
```
### AdropSplashAdView Properties
| Property | Type | Description |
| - | - | - |
| `unitId` | `String` | Ad unit ID (read-only) |
| `creativeId` | `String` | Ad creative ID (read-only) |
| `isClosed` | `Boolean` | Whether splash ad is closed (read-only) |
| `txId` | `String` | Transaction ID (read-only) |
| `campaignId` | `String` | Campaign ID (read-only) |
### AdropSplashAdView Methods
| Method | Description |
| - | - |
| `load()` | Loads and displays the ad |
| `destroy()` | Releases resources (timers, visibility tracker, listener). Call when splash ad view is no longer needed. |
***
## Listeners
### AdropSplashAdListener
Listener for when using `AdropSplashAd`.
| Method | Required | Description |
| - | - | - |
| `onAdReceived(ad)` | Optional | Called when ad is received successfully |
| `onAdFailedToReceive(ad, errorCode)` | Optional | Called when ad fails to load |
| `onAdImpression(ad)` | Optional | Called when ad impression is recorded |
| `onAdClose(ad, impressed)` | **Required** | Called when splash ad closes |
### AdropSplashAdViewListener
Listener for when using `AdropSplashAdView`.
| Method | Required | Description |
| - | - | - |
| `onAdReceived(ad)` | Optional | Called when ad is received successfully |
| `onAdFailedToReceive(ad, errorCode)` | Optional | Called when ad fails to load |
| `onAdImpression(ad)` | Optional | Called when ad impression is recorded |
| `onAdClose(ad, impressed)` | **Required** | Called when splash ad closes. Must handle main screen transition |
When using `AdropSplashAdView`, you must implement the `onAdClose` method to handle the transition to the main screen.
***
## impressed Parameter
The `impressed` parameter in `onAdClose(ad, impressed)` indicates whether the ad was actually displayed.
```kotlin theme={null}
override fun onAdClose(ad: AdropSplashAdView, impressed: Boolean) {
if (impressed) {
println("Ad was displayed to user")
// Post-impression logic
} else {
println("Ad was not displayed (load failure or skip)")
// Non-impression handling logic
}
// Navigate to main screen
goToMainActivity()
}
```
### Cases When impressed is false
* Ad load failure
* Network error
* User skipped before viewing ad
* Insufficient ad inventory
***
## Best Practices
### 1. Set Appropriate Timers
Splash ads should not be displayed too short or too long.
```xml theme={null}
10002000
```
### 2. Optimize Logo Image
Prepare your app logo in appropriate sizes to minimize loading time.
```
drawable-mdpi/splash_logo.png (192px × 192px)
drawable-hdpi/splash_logo.png (288px × 288px)
drawable-xhdpi/splash_logo.png (384px × 384px)
drawable-xxhdpi/splash_logo.png (576px × 576px)
drawable-xxxhdpi/splash_logo.png (768px × 768px)
```
### 3. Handle Failures
Ensure the app starts normally even when ad loading fails.
```kotlin theme={null}
override fun onAdFailedToReceive(ad: AdropSplashAdView, errorCode: AdropErrorCode) {
println("Ad load failed: $errorCode")
// Navigate to main screen even on failure
goToMainActivity()
}
```
### 4. Distinguish Test Environment
Distinguish between development and production environments for testing.
```kotlin theme={null}
val splashUnitId = if (BuildConfig.DEBUG) {
"PUBLIC_TEST_UNIT_ID_SPLASH"
} else {
"YOUR_PRODUCTION_SPLASH_UNIT_ID"
}
// Initialize SDK
Adrop.initialize(application, production = !BuildConfig.DEBUG)
```
### 5. Prevent Memory Leaks
Clean up listeners when Activity is destroyed.
```kotlin theme={null}
override fun onDestroy() {
splashAdView.destroy()
super.onDestroy()
}
```
***
## Testing
### Test Unit ID
Use the test unit ID during development.
```kotlin theme={null}
// Test unit ID
val testUnitId = "PUBLIC_TEST_UNIT_ID_SPLASH"
```
Be sure to use the actual unit ID created in the Adrop console for production deployment. Deploying with test unit IDs will not generate ad revenue.
### Debugging
Check ad events with logs.
```kotlin theme={null}
splashAdView.listener = object : AdropSplashAdViewListener {
override fun onAdReceived(ad: AdropSplashAdView) {
Log.d("AdropSplash", "Ad received successfully")
Log.d("AdropSplash", " - unitId: ${ad.unitId}")
Log.d("AdropSplash", " - creativeId: ${ad.creativeId}")
}
override fun onAdFailedToReceive(ad: AdropSplashAdView, errorCode: AdropErrorCode) {
Log.e("AdropSplash", "Ad receive failed: $errorCode")
Log.e("AdropSplash", " - Check network status")
Log.e("AdropSplash", " - Verify unit ID: ${ad.unitId}")
}
override fun onAdImpression(ad: AdropSplashAdView) {
Log.d("AdropSplash", "Ad impression")
Log.d("AdropSplash", " - txId: ${ad.txId}")
Log.d("AdropSplash", " - campaignId: ${ad.campaignId}")
}
override fun onAdClose(ad: AdropSplashAdView, impressed: Boolean) {
Log.d("AdropSplash", "Ad closed")
Log.d("AdropSplash", " - impressed: $impressed")
Log.d("AdropSplash", " - isClosed: ${ad.isClosed}")
}
}
```
***
## Troubleshooting
* Verify that `AdropSplashAdActivity` or custom SplashActivity is set as LAUNCHER in AndroidManifest.xml
* Check `android:exported="true"` setting
* Verify SDK initialization is complete (Application class)
* Check that `adrop_service.json` file is in the `assets` folder
* Check network connection status
* Verify the unit ID is correct (strings.xml or code)
* Verify `production = false` setting in test environment
* Check `onAdFailedToReceive` error code
* Verify `adrop_service.json` file content is correct
* Verify `adrop_splash_ad_next_activity` setting is correct (full package path)
* Check `onAdClose` implementation when using `AdropSplashAdView`
* Verify Activity's full qualified name (e.g., com.myapp.MainActivity)
* Verify filename is exactly `activity_adrop_splash_ad.xml`
* Check that an ImageView with `android:id="@+id/adrop_splash_ad_image"` ID exists
* Clean build and re-run the app (Build > Clean Project > Rebuild Project)
This is normal. The system splash screen is not supported on devices below Android 12, and `AdropSplashAdActivity` is displayed directly.
***
## Next Steps
Display full-screen ads during screen transitions
Implement banner ads
Custom design native ads
Provide user rewards
# Targeting Settings
Source: https://docs.adrop.io/sdk/android/targeting
Learn how to display customized ads to specific user groups through targeting in Android SDK.
## Overview
Through targeting settings, you can display customized ads to specific user groups. The Adrop SDK provides two targeting methods:
* **Audience Targeting**: Display ads based on user properties.
* **Contextual Targeting**: Display ads based on content context (category, topic, etc.).
To collect targeting data, UID and properties must be set before loading ads.
***
## UID Setting
Set a user identifier (UID) to distinguish users. UID is the foundation for targeted advertising.
### Usage
```kotlin Kotlin theme={null}
import io.adrop.ads.Adrop
// After user login
Adrop.setUID("user_123")
```
```java Java theme={null}
import io.adrop.ads.Adrop;
// After user login
Adrop.INSTANCE.setUID("user_123");
```
### Parameters
| Parameter | Type | Description |
| - | - | - |
| `uid` | `String` | User unique identifier (e.g., service member ID) |
UID is hashed with SHA-256 before transmission. Do not pass personal information (email, phone number, etc.) directly.
***
## Audience Targeting
Collect user property information to display ads to specific user groups.
### Setting Properties
Use the `AdropMetrics.setProperty()` method to collect user property information.
```kotlin Kotlin theme={null}
import io.adrop.ads.metrics.AdropMetrics
// String property
AdropMetrics.setProperty("membership_level", "premium")
// Numeric property
AdropMetrics.setProperty("booking_count", 15)
// Boolean property
AdropMetrics.setProperty("is_subscriber", true)
// Pass null (remove property)
AdropMetrics.setProperty("membership_level", null)
```
```java Java theme={null}
import io.adrop.ads.metrics.AdropMetrics;
// String property
AdropMetrics.setProperty("membership_level", "premium");
// Numeric property
AdropMetrics.setProperty("booking_count", 15);
// Boolean property
AdropMetrics.setProperty("is_subscriber", true);
// Pass null (remove property)
AdropMetrics.setProperty("membership_level", null);
```
### Parameters
| Parameter | Type | Description |
| - | - | - |
| `key` | `String` | Property key (max 64 characters) |
| `value` | `Any?` | Property value (String, Int, Double, Long, Boolean, null) |
* Property key is limited to 64 characters maximum.
* String values are limited to 256 characters maximum.
* Numeric values are limited to 9007199254740991 maximum.
* Up to 256 properties can be set.
### Default Properties
The Adrop SDK provides default properties for targeting. Use the `AdropKey` and `AdropValue` classes to set values.
#### Age (Birthday)
When birthday information is provided, age is automatically calculated.
```kotlin Kotlin theme={null}
import io.adrop.ads.metrics.AdropMetrics
import io.adrop.ads.model.AdropKey
import io.adrop.ads.model.AdropValue
// Year only (yyyy)
AdropMetrics.setProperty(AdropKey.BIRTH, "1990")
// Year and month (yyyyMM)
AdropMetrics.setProperty(AdropKey.BIRTH, "199003")
// Year, month, and day (yyyyMMdd)
AdropMetrics.setProperty(AdropKey.BIRTH, "19900315")
```
```java Java theme={null}
import io.adrop.ads.metrics.AdropMetrics;
import io.adrop.ads.model.AdropKey;
import io.adrop.ads.model.AdropValue;
// Year only (yyyy)
AdropMetrics.setProperty(AdropKey.BIRTH, "1990");
// Year and month (yyyyMM)
AdropMetrics.setProperty(AdropKey.BIRTH, "199003");
// Year, month, and day (yyyyMMdd)
AdropMetrics.setProperty(AdropKey.BIRTH, "19900315");
```
**Date Format Constants**
| Constant | Value | Description |
| - | - | - |
| `AdropValue.AdropBirth.formatYear` | `"yyyy"` | Year only (e.g., "1990") |
| `AdropValue.AdropBirth.formatYearMonth` | `"yyyyMM"` | Year and month (e.g., "199003") |
| `AdropValue.AdropBirth.formatYearMonthDay` | `"yyyyMMdd"` | Year, month, and day (e.g., "19900315") |
#### Gender
```kotlin Kotlin theme={null}
import io.adrop.ads.metrics.AdropMetrics
import io.adrop.ads.model.AdropKey
import io.adrop.ads.model.AdropValue
// Male
AdropMetrics.setProperty(AdropKey.GENDER, AdropValue.AdropGender.MALE)
// Female
AdropMetrics.setProperty(AdropKey.GENDER, AdropValue.AdropGender.FEMALE)
// Other
AdropMetrics.setProperty(AdropKey.GENDER, AdropValue.AdropGender.OTHER)
// Unknown
AdropMetrics.setProperty(AdropKey.GENDER, AdropValue.UNKNOWN)
```
```java Java theme={null}
import io.adrop.ads.metrics.AdropMetrics;
import io.adrop.ads.model.AdropKey;
import io.adrop.ads.model.AdropValue;
// Male
AdropMetrics.setProperty(AdropKey.GENDER, AdropValue.AdropGender.MALE);
// Female
AdropMetrics.setProperty(AdropKey.GENDER, AdropValue.AdropGender.FEMALE);
// Other
AdropMetrics.setProperty(AdropKey.GENDER, AdropValue.AdropGender.OTHER);
// Unknown
AdropMetrics.setProperty(AdropKey.GENDER, AdropValue.UNKNOWN);
```
**Gender Value Constants**
| Constant | Value | Description |
| - | - | - |
| `AdropValue.AdropGender.MALE` | `"M"` | Male |
| `AdropValue.AdropGender.FEMALE` | `"F"` | Female |
| `AdropValue.AdropGender.OTHER` | `"O"` | Other |
| `AdropValue.UNKNOWN` | `"U"` | Unknown |
#### Setting Age Directly
You can also set age directly instead of birthday.
```kotlin Kotlin theme={null}
import io.adrop.ads.metrics.AdropMetrics
import io.adrop.ads.model.AdropKey
AdropMetrics.setProperty(AdropKey.AGE, 30)
```
```java Java theme={null}
import io.adrop.ads.metrics.AdropMetrics;
import io.adrop.ads.model.AdropKey;
AdropMetrics.setProperty(AdropKey.AGE, 30);
```
You only need to set either `BIRTH` or `AGE`. If both are set, `BIRTH` takes precedence.
### Custom Properties
You can set custom properties for your service. Custom properties must first be defined in the **Property** menu of the [Adrop console](https://console.adrop.io).
```kotlin Kotlin theme={null}
import io.adrop.ads.metrics.AdropMetrics
// Local activity app example
AdropMetrics.setProperty("region", "Seoul")
AdropMetrics.setProperty("booking_count", 5)
// Shopping mall app example
AdropMetrics.setProperty("membership_tier", "gold")
AdropMetrics.setProperty("total_purchase_amount", 1500000)
// Media app example
// Note: Array values are not supported. Use JSON string instead.
AdropMetrics.setProperty("favorite_genres", """["drama","action"]""")
AdropMetrics.setProperty("is_premium_subscriber", true)
```
```java Java theme={null}
import io.adrop.ads.metrics.AdropMetrics;
// Local activity app example
AdropMetrics.setProperty("region", "Seoul");
AdropMetrics.setProperty("booking_count", 5);
// Shopping mall app example
AdropMetrics.setProperty("membership_tier", "gold");
AdropMetrics.setProperty("total_purchase_amount", 1500000);
// Media app example
// Note: Array values are not supported. Use JSON string instead.
AdropMetrics.setProperty("favorite_genres", "[\"drama\",\"action\"]");
AdropMetrics.setProperty("is_premium_subscriber", true);
```
Custom property names must exactly match the names defined in the console. Case-sensitive.
### Property Usage Example
Example of updating properties when a user enters a specific screen in the app.
```kotlin Kotlin theme={null}
import io.adrop.ads.Adrop
import io.adrop.ads.metrics.AdropMetrics
import io.adrop.ads.model.AdropKey
import io.adrop.ads.model.AdropValue
class ProfileActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_profile)
// Get user info
val user = getUserInfo()
// Set UID
Adrop.setUID(user.id)
// Set default properties
AdropMetrics.setProperty(AdropKey.BIRTH, user.birthDate)
AdropMetrics.setProperty(AdropKey.GENDER, user.gender)
// Set custom properties
AdropMetrics.setProperty("membership_level", user.membershipLevel)
AdropMetrics.setProperty("total_booking_count", user.bookingCount)
}
}
```
```java Java theme={null}
import io.adrop.ads.Adrop;
import io.adrop.ads.metrics.AdropMetrics;
import io.adrop.ads.model.AdropKey;
import io.adrop.ads.model.AdropValue;
public class ProfileActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_profile);
// Get user info
User user = getUserInfo();
// Set UID
Adrop.INSTANCE.setUID(user.getId());
// Set default properties
AdropMetrics.setProperty(AdropKey.BIRTH, user.getBirthDate());
AdropMetrics.setProperty(AdropKey.GENDER, user.getGender());
// Set custom properties
AdropMetrics.setProperty("membership_level", user.getMembershipLevel());
AdropMetrics.setProperty("total_booking_count", user.getBookingCount());
}
}
```
***
## Contextual Targeting
Display ads based on content context (category, topic, etc.). Use the `contextId` property in banner ads and native ads.
Context targeting applies only to banner and native ad requests that include a Context ID (banner and native ads in the Android and iOS SDKs, the Web SDK, and the REST API). Interstitial, rewarded, popup, and splash ads, as well as the Flutter and React Native SDKs, do not send a Context ID. If an ad product that contains these placements uses context targeting, campaigns that include context options are not shown in those placements, and context exclusions are not applied there.
### Banner Ads
#### Setting in XML
```xml XML theme={null}
```
#### Setting in Code
```kotlin Kotlin theme={null}
import io.adrop.ads.banner.AdropBanner
val adropBanner = AdropBanner(this, "PUBLIC_TEST_UNIT_ID_320_50", "article_sports")
// contextId can only be set in the constructor
// For banners placed in XML, set it with the app:adrop_context_id attribute (cannot be changed after creation)
```
```java Java theme={null}
import io.adrop.ads.banner.AdropBanner;
AdropBanner adropBanner = new AdropBanner(this, "PUBLIC_TEST_UNIT_ID_320_50", "article_sports");
// contextId can only be set in the constructor
// For banners placed in XML, set it with the app:adrop_context_id attribute (cannot be changed after creation)
```
### Native Ads
Native ads can set `contextId` in the constructor.
```kotlin Kotlin theme={null}
import io.adrop.ads.nativeAd.AdropNativeAd
import io.adrop.ads.nativeAd.AdropNativeAdListener
// contextId is set via constructor
val nativeAd = AdropNativeAd(this, "PUBLIC_TEST_UNIT_ID_NATIVE", "article_technology")
nativeAd.listener = object : AdropNativeAdListener {
override fun onAdReceived(ad: AdropNativeAd) {
// Ad received successfully
}
override fun onAdFailedToReceive(ad: AdropNativeAd, errorCode: AdropErrorCode) {
// Ad failed to receive
}
}
// load() takes no parameters
nativeAd.load()
```
```java Java theme={null}
import io.adrop.ads.nativeAd.AdropNativeAd;
import io.adrop.ads.nativeAd.AdropNativeAdListener;
// contextId is set via constructor
AdropNativeAd nativeAd = new AdropNativeAd(this, "PUBLIC_TEST_UNIT_ID_NATIVE", "article_technology");
nativeAd.setListener(new AdropNativeAdListener() {
@Override
public void onAdReceived(@NonNull AdropNativeAd ad) {
// Ad received successfully
}
@Override
public void onAdFailedToReceive(@NonNull AdropNativeAd ad, AdropErrorCode errorCode) {
// Ad failed to receive
}
});
// load() takes no parameters
nativeAd.load();
```
### Context ID Usage Example
Example of displaying ads based on article category in a news app.
```kotlin Kotlin theme={null}
import io.adrop.ads.banner.AdropBanner
class ArticleActivity : AppCompatActivity() {
private lateinit var adropBanner: AdropBanner
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_article)
// Get article category
val articleCategory = getArticleCategory() // "sports", "technology", "entertainment", etc.
// Create banner with Context ID matching category
val contextId = "article_$articleCategory"
adropBanner = AdropBanner(this, "YOUR_UNIT_ID", contextId)
// Add to layout
val container = findViewById(R.id.ad_container)
container.addView(adropBanner)
// Load ad
adropBanner.load()
}
}
```
```java Java theme={null}
import io.adrop.ads.banner.AdropBanner;
public class ArticleActivity extends AppCompatActivity {
private AdropBanner adropBanner;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_article);
// Get article category
String articleCategory = getArticleCategory(); // "sports", "technology", "entertainment", etc.
// Create banner with Context ID matching category
String contextId = "article_" + articleCategory;
adropBanner = new AdropBanner(this, "YOUR_UNIT_ID", contextId);
// Add to layout
ViewGroup container = findViewById(R.id.ad_container);
container.addView(adropBanner);
// Load ad
adropBanner.load();
}
}
```
* Context IDs are case-sensitive and must exactly match a value entered in a context targeting option's condition in the console.
* Do not use commas (,) in Context IDs, because a comma separates multiple values both in the console and in an ad request. When requesting ads, pass the value exactly as entered in the console, without leading or trailing spaces.
* To match more than one context in a single request, pass a comma-separated value such as `hiking,outdoor`. The first 5 IDs are used, and exclusion takes precedence over inclusion. See [Passing Multiple Context IDs](/targeting/context#passing-multiple-context-ids).
* If you don't pass a Context ID, or pass a value that isn't registered in the console, no error occurs. Only campaigns with context targeting are excluded, and other ads can still be served.
***
## Event Tracking
Track user behavior events to build audiences based on user actions. Use `AdropMetrics.sendEvent()` to send events.
`logEvent()` is deprecated. Use `sendEvent()` instead.
### Sending Events
```kotlin Kotlin theme={null}
import io.adrop.ads.metrics.AdropMetrics
import io.adrop.ads.metrics.AdropEventParam
// Simple event (no parameters)
AdropMetrics.sendEvent("app_open")
// Event with parameters
val params = AdropEventParam.Builder()
.putString("item_id", "SKU-123")
.putString("item_name", "Widget")
.putFloat("price", 29.99f)
.build()
AdropMetrics.sendEvent("view_item", params)
```
```java Java theme={null}
import io.adrop.ads.metrics.AdropMetrics;
import io.adrop.ads.metrics.AdropEventParam;
// Simple event (no parameters)
AdropMetrics.sendEvent("app_open");
// Event with parameters
AdropEventParam params = new AdropEventParam.Builder()
.putString("item_id", "SKU-123")
.putString("item_name", "Widget")
.putFloat("price", 29.99f)
.build();
AdropMetrics.sendEvent("view_item", params);
```
### Multi-Item Events
For events that involve multiple items (e.g., purchase, begin\_checkout), use `putItems()` to attach an item list.
```kotlin Kotlin theme={null}
import io.adrop.ads.metrics.AdropMetrics
import io.adrop.ads.metrics.AdropEventParam
val item1 = AdropEventParam.Builder()
.putString("item_id", "A")
.putString("item_name", "Product A")
.putInt("price", 100)
.putInt("quantity", 1)
.build()
val item2 = AdropEventParam.Builder()
.putString("item_id", "B")
.putString("item_name", "Product B")
.putInt("price", 200)
.putInt("quantity", 2)
.build()
val params = AdropEventParam.Builder()
.putString("tx_id", "tx_123")
.putString("currency", "KRW")
.putItems(listOf(item1, item2))
.build()
AdropMetrics.sendEvent("purchase", params)
```
```java Java theme={null}
import io.adrop.ads.metrics.AdropMetrics;
import io.adrop.ads.metrics.AdropEventParam;
AdropEventParam item1 = new AdropEventParam.Builder()
.putString("item_id", "A")
.putString("item_name", "Product A")
.putInt("price", 100)
.putInt("quantity", 1)
.build();
AdropEventParam item2 = new AdropEventParam.Builder()
.putString("item_id", "B")
.putString("item_name", "Product B")
.putInt("price", 200)
.putInt("quantity", 2)
.build();
AdropEventParam params = new AdropEventParam.Builder()
.putString("tx_id", "tx_123")
.putString("currency", "KRW")
.putItems(java.util.Arrays.asList(item1, item2))
.build();
AdropMetrics.sendEvent("purchase", params);
```
### Supported Event Examples
| Event Name | Description | Common Parameters |
| - | - | - |
| `app_open` | App opened | — |
| `sign_up` | User signed up | `method` |
| `view_item` | Viewed item | `item_id`, `item_name`, `price` |
| `add_to_cart` | Added to cart | `item_id`, `item_name`, `price`, `quantity` |
| `add_to_wishlist` | Added to wishlist | `item_id`, `item_name`, `price` |
| `begin_checkout` | Began checkout | `currency`, `items` |
| `purchase` | Purchase completed | `tx_id`, `currency`, `items` |
| `page_view` | Page viewed | `page_id`, `page_category` |
| `click` | Element clicked | `element_id`, `element_type` |
* Event name must be 1–64 characters.
* Parameter key must be 1–64 characters.
* String parameter values are limited to 1024 characters.
* Duplicate events with the same name and parameters are throttled within a 500ms window.
***
## Best Practices
### 1. Set UID Before Loading Ads
```kotlin Kotlin theme={null}
// Correct example
Adrop.setUID("user_123")
adropBanner.load()
// Wrong example
adropBanner.load()
Adrop.setUID("user_123") // Targeting won't be applied if set after ad load
```
```java Java theme={null}
// Correct example
Adrop.INSTANCE.setUID("user_123");
adropBanner.load();
// Wrong example
adropBanner.load();
Adrop.INSTANCE.setUID("user_123"); // Targeting won't be applied if set after ad load
```
### 2. Use Meaningful Context IDs
```kotlin Kotlin theme={null}
// Correct example
val contextId = "article_${article.category}" // "article_sports", "article_tech"
val adropBanner = AdropBanner(this, unitId, contextId)
// Wrong example
val contextId = "page_${randomNumber}" // Meaningless random value
val adropBanner = AdropBanner(this, unitId, contextId)
```
```java Java theme={null}
// Correct example
String contextId = "article_" + article.getCategory(); // "article_sports", "article_tech"
AdropBanner adropBanner = new AdropBanner(this, unitId, contextId);
// Wrong example
String contextId = "page_" + randomNumber; // Meaningless random value
AdropBanner adropBanner = new AdropBanner(this, unitId, contextId);
```
### 3. Update Properties Whenever They Change
```kotlin Kotlin theme={null}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Set initial properties on app start
updateUserProperties()
}
fun onUserPurchaseComplete(purchaseAmount: Int) {
// Update properties on purchase completion
val currentTotal = getCurrentTotalPurchase()
AdropMetrics.setProperty("total_purchase_amount", currentTotal + purchaseAmount)
}
fun onMembershipUpgrade(newTier: String) {
// Update properties on membership upgrade
AdropMetrics.setProperty("membership_tier", newTier)
}
}
```
```java Java theme={null}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Set initial properties on app start
updateUserProperties();
}
public void onUserPurchaseComplete(int purchaseAmount) {
// Update properties on purchase completion
int currentTotal = getCurrentTotalPurchase();
AdropMetrics.setProperty("total_purchase_amount", currentTotal + purchaseAmount);
}
public void onMembershipUpgrade(String newTier) {
// Update properties on membership upgrade
AdropMetrics.setProperty("membership_tier", newTier);
}
}
```
***
## Related Documentation
Create audience targeting in console
Create context targeting in console
Set up targeting category sales
Implement banner ads
Implement native ads
Classes, listeners, error codes
***
## FAQ
Ads will still be displayed even without setting UID, but audience targeting won't be applied. Only basic targeting (country, language, etc.) will be used. Setting UID is essential for sophisticated targeting.
Property data is reflected in the console within up to 24 hours after collection. If data is not displayed:
1. Verify that properties are correctly defined in the console.
2. Verify that property keys passed from SDK exactly match the console (case-sensitive).
3. Verify that `AdropMetrics.setProperty()` is called before loading ads.
Context ID should be a meaningful string representing the content context. For example:
* News app: `"article_sports"`, `"article_tech"`, `"article_entertainment"`
* Shopping app: `"category_fashion"`, `"category_electronics"`
* Video app: `"video_drama"`, `"video_comedy"`
Set it to exactly match, including case, a value entered in a context targeting option's condition in the console. Do not use commas (,), because a comma separates multiple values both in the console and in an ad request, and do not add leading or trailing spaces.
No, array values are not directly supported. `setProperty` accepts String, Int, Double, Long, Boolean, and null values only. To store multiple values, use a JSON string:
```kotlin theme={null}
AdropMetrics.setProperty("favorite_genres", """["drama","action","comedy"]""")
```
Pass `null` to the property value to delete that property:
```kotlin theme={null}
AdropMetrics.setProperty("membership_level", null)
```
# UMP Integration
Source: https://docs.adrop.io/sdk/android/ump
Integrate Google UMP for GDPR/CCPA compliance on Android
## Overview
The User Messaging Platform (UMP) SDK helps you manage user consent for personalized advertising in compliance with GDPR (General Data Protection Regulation) and CCPA (California Consumer Privacy Act).
UMP integration requires the `adrop-ads-backfill` module. Make sure you have completed the [Getting Started](/sdk/android/overview) installation guide first.
***
## Basic Usage
Use the Consent Manager to request user consent after initializing Adrop.
```kotlin Kotlin theme={null}
import io.adrop.ads.Adrop
import io.adrop.ads.consent.AdropConsentListener
import io.adrop.ads.consent.AdropConsentResult
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Call after Adrop.initialize()
Adrop.consentManager?.requestConsentInfoUpdate(this, object : AdropConsentListener {
override fun onConsentInfoUpdated(result: AdropConsentResult) {
if (result.error != null) {
Log.e("Consent", "Error: ${result.error}")
}
}
})
}
}
```
```java Java theme={null}
import io.adrop.ads.Adrop;
import io.adrop.ads.consent.AdropConsentListener;
import io.adrop.ads.consent.AdropConsentResult;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Call after Adrop.initialize()
if (Adrop.getConsentManager() != null) {
Adrop.getConsentManager().requestConsentInfoUpdate(this, new AdropConsentListener() {
@Override
public void onConsentInfoUpdated(AdropConsentResult result) {
if (result.getError() != null) {
Log.e("Consent", "Error: " + result.getError());
}
}
});
}
}
}
```
***
## Consent Status
The consent manager returns one of the following statuses:
| Status | Description |
| - | - |
| `UNKNOWN` | Consent status not yet determined |
| `REQUIRED` | Consent required (popup will be shown) |
| `NOT_REQUIRED` | Consent not required (non-GDPR region) |
| `OBTAINED` | Consent already obtained |
***
## Privacy Options
Users must be able to change their consent after the first prompt. When UMP reports that a privacy options entry point is required, add one to your app — typically a **Privacy settings** item in your settings screen — and open the privacy options form from it.
Requires `adrop-ads` and `adrop-ads-backfill` 1.15.0 or later. Keep both on the same version — with an older backfill the status always returns `UNKNOWN` and the form is not shown.
### Check whether an entry point is required
```kotlin Kotlin theme={null}
import io.adrop.ads.consent.AdropPrivacyOptionsRequirementStatus
class SettingsActivity : AppCompatActivity() {
// Re-check whenever your settings screen appears
override fun onResume() {
super.onResume()
val status = Adrop.consentManager?.getPrivacyOptionsRequirementStatus(this)
// Show the entry point only when it is required
privacySettingsButton.visibility =
if (status == AdropPrivacyOptionsRequirementStatus.REQUIRED) View.VISIBLE else View.GONE
}
}
```
```java Java theme={null}
import io.adrop.ads.consent.AdropPrivacyOptionsRequirementStatus;
public class SettingsActivity extends AppCompatActivity {
// Re-check whenever your settings screen appears
@Override
protected void onResume() {
super.onResume();
if (Adrop.getConsentManager() == null) return;
AdropPrivacyOptionsRequirementStatus status =
Adrop.getConsentManager().getPrivacyOptionsRequirementStatus(this);
// Show the entry point only when it is required
privacySettingsButton.setVisibility(
status == AdropPrivacyOptionsRequirementStatus.REQUIRED ? View.VISIBLE : View.GONE);
}
}
```
| Status | Description |
| - | - |
| `UNKNOWN` | Not determined yet — `requestConsentInfoUpdate()` has not completed in this app session, or the installed backfill does not support privacy options |
| `REQUIRED` | An entry point must be shown |
| `NOT_REQUIRED` | No entry point is needed |
Call `requestConsentInfoUpdate()` on **every app launch**, including for users who already gave consent. The requirement status reflects the current app session only after that call completes — otherwise returning users never see the entry point. The consent form itself is still shown only when it is needed.
### Show the privacy options form
```kotlin Kotlin theme={null}
// Open the form from your "Privacy settings" entry point (inside your activity)
private fun showPrivacyOptionsForm() {
Adrop.consentManager?.showPrivacyOptionsForm(this, object : AdropConsentListener {
override fun onConsentInfoUpdated(result: AdropConsentResult) {
if (result.error != null) {
Log.e("Consent", "Error: ${result.error}")
return
}
// Consent may have changed — re-check whether the entry point is still required
updatePrivacySettingsVisibility()
}
})
}
```
```java Java theme={null}
// Open the form from your "Privacy settings" entry point (inside your activity)
private void showPrivacyOptionsForm() {
if (Adrop.getConsentManager() == null) return;
Adrop.getConsentManager().showPrivacyOptionsForm(this, new AdropConsentListener() {
@Override
public void onConsentInfoUpdated(AdropConsentResult result) {
if (result.getError() != null) {
Log.e("Consent", "Error: " + result.getError());
return;
}
// Consent may have changed — re-check whether the entry point is still required
updatePrivacySettingsVisibility();
}
});
}
```
When the form closes, the SDK applies the updated consent and then returns an `AdropConsentResult`. The form opens only while an entry point is required; otherwise the stored consent is left unchanged and the result carries the current state with an error.
The listener is also called when the activity has already finished — for example when the user leaves the screen while the form is open. Check `isFinishing` / `isDestroyed` before touching activity UI from the callback.
***
## Debug Settings (Test Mode)
Test GDPR/CCPA consent flows during development:
```kotlin Kotlin theme={null}
import io.adrop.ads.consent.AdropConsentDebugGeography
if (BuildConfig.DEBUG) {
// Set debug geography before requesting consent
Adrop.consentManager?.setDebugSettings(
testDeviceHashedIds = listOf("YOUR_HASHED_DEVICE_ID"), // Check Logcat
geography = AdropConsentDebugGeography.EEA // Test GDPR
)
// Reset consent for testing
Adrop.consentManager?.reset(this)
}
```
```java Java theme={null}
import io.adrop.ads.consent.AdropConsentDebugGeography;
if (BuildConfig.DEBUG) {
// Set debug geography before requesting consent
Adrop.getConsentManager().setDebugSettings(
Arrays.asList("YOUR_HASHED_DEVICE_ID"), // Check Logcat
AdropConsentDebugGeography.EEA // Test GDPR
);
// Reset consent for testing
Adrop.getConsentManager().reset(this);
}
```
### Debug Geographies
| Geography | Description |
| - | - |
| `DISABLED` | Use actual device location |
| `EEA` | Test GDPR (European Economic Area) |
| `REGULATED_US_STATE` | Test CCPA (California, etc.) |
| `OTHER` | Test non-regulated regions |
Debug settings should only be used during development. Remove or disable them before releasing to production.
`reset()` is for testing only — never call it in production code. To let users change their consent, use `showPrivacyOptionsForm()`.
***
## Best Practices
Request consent as early as possible in your app lifecycle, ideally right after SDK initialization, and call it on every app launch.
Always handle consent errors gracefully and provide fallback behavior.
Use debug settings to test all consent scenarios (required, not required, obtained) before release.
When the privacy options status is required, show an entry point in your settings screen so users can change their consent at any time.
***
## Related Documentation
Set up user and context targeting
SDK installation and setup guide
Classes, listeners, and error codes
# WebView Guide
Source: https://docs.adrop.io/sdk/android/webview
How to display backfill ads in WebView on Android
## Overview
Display backfill ads in your WebView by registering it with the Adrop SDK. This allows web content within your app to show ads through the Adrop platform.
***
## AndroidManifest.xml Configuration
Add the following to your `AndroidManifest.xml` to enable backfill ads in WebView:
```xml AndroidManifest.xml theme={null}
```
This configuration is required for backfill ads to work properly in WebView. Without it, backfill ads may not display.
***
## Register WebView
```kotlin Kotlin theme={null}
import android.webkit.WebView
import io.adrop.ads.Adrop
class WebViewActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val webView = findViewById(R.id.webView)
// Register WebView for backfill ads support
// Call as early as possible, before loading content
Adrop.registerWebView(webView)
// Load your web content
webView.loadUrl("https://your-website.com")
}
}
```
```java Java theme={null}
import android.webkit.WebView;
import io.adrop.ads.Adrop;
public class WebViewActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
WebView webView = findViewById(R.id.webView);
// Register WebView for backfill ads support
// Call as early as possible, before loading content
Adrop.registerWebView(webView);
// Load your web content
webView.loadUrl("https://your-website.com");
}
}
```
`registerWebView()` automatically configures optimal WebView settings:
* JavaScript enabled
* DOM storage enabled
* Media playback without user gesture
* Third-party cookies enabled
Call this method on the main thread, as early as possible (e.g., in `onCreate`).
Backfill ads in WebView requires `adrop-ads-backfill` module. If the module is not installed, `registerWebView()` is silently ignored.
***
## Handling External URLs
If your app opens non-own-domain URLs in an external browser, you must ensure that ad resource requests (e.g., `googleads.g.doubleclick.net`) are not blocked.
Ad resources such as iframes and scripts are loaded automatically by the ad SDK — they are not user-initiated navigations. Only redirect **user-initiated main frame navigations** to an external browser.
```kotlin Kotlin theme={null}
import android.content.Intent
import android.net.Uri
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
val allowedHost = "my-domain.com"
webView.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
val url = request.url
val host = url.host ?: ""
// Only redirect user-initiated navigations to external browser
// Ad resources (e.g., googleads.g.doubleclick.net) load via iframes/scripts,
// not user gestures — do not block them
if (!host.contains(allowedHost) && request.hasGesture()) {
view.context.startActivity(Intent(Intent.ACTION_VIEW, url))
return true
}
return false
}
}
```
```java Java theme={null}
import android.content.Intent;
import android.net.Uri;
import android.webkit.WebResourceRequest;
import android.webkit.WebView;
import android.webkit.WebViewClient;
String allowedHost = "my-domain.com";
webView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
Uri url = request.getUrl();
String host = url.getHost() != null ? url.getHost() : "";
// Only redirect user-initiated navigations to external browser
// Ad resources (e.g., googleads.g.doubleclick.net) load via iframes/scripts,
// not user gestures — do not block them
if (!host.contains(allowedHost) && request.hasGesture()) {
view.getContext().startActivity(new Intent(Intent.ACTION_VIEW, url));
return true;
}
return false;
}
});
```
Do not block or redirect all non-own-domain requests. Ad resources like `googleads.g.doubleclick.net` are loaded automatically during the backfill ad process and must be allowed to load within the WebView.
***
## Related Documentation
Learn how to integrate banner ads
API reference
Example repository
# Getting Started
Source: https://docs.adrop.io/sdk/flutter/overview
A guide to integrating Adrop SDK into your Flutter app.
## Overview
Adrop Flutter SDK allows you to easily integrate various ad formats into your Flutter app.
### Supported Ad Formats
| Format | Description |
| - | - |
| Banner Ad | Rectangular ads displayed in a portion of the screen |
| Native Ad | Ads that naturally blend with your app content |
| Interstitial Ad | Full-screen ads |
| Rewarded Ad | Full-screen ads that provide rewards |
| Popup Ad | Ads displayed in popup format |
| Splash Ad | Ads displayed with your logo when the app starts |
***
## Requirements
### Flutter
* Flutter SDK **3.3.0** or higher
* Dart SDK **2.18.0** or higher
### Android
* API level **23** (Android 6.0) or higher
* `compileSdkVersion` **34**
* Kotlin **2.1.0** or higher
* Gradle **8.7** or higher
* Using Jetpack (AndroidX)
### iOS
* iOS **13.0** or higher
* Swift **5.0** or higher
***
## Installation
### 1. Install Package
Run the following command in your project directory.
```bash theme={null}
flutter pub add adrop_ads_flutter
```
### 2. Android Setup
Add the backfill ad repository to `android/settings.gradle.kts`.
```kotlin android/settings.gradle.kts theme={null}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven { url = uri("https://artifact.bytedance.com/repository/pangle") }
}
}
```
Add the backfill dependency to `android/app/build.gradle`.
```gradle android/app/build.gradle theme={null}
dependencies {
implementation 'io.adrop:adrop-ads-backfill:1.15.0'
}
```
Add the backfill App ID to `android/app/src/main/AndroidManifest.xml`.
```xml android/app/src/main/AndroidManifest.xml theme={null}
```
Please contact Adrop to get your App ID.
### 3. iOS Setup
Modify `ios/Podfile`.
```ruby ios/Podfile theme={null}
use_frameworks!
target 'Runner' do
# Other dependencies...
# Backfill ad SDK
pod 'adrop-ads-backfill'
end
```
Install pods.
```bash theme={null}
cd ios && pod install --repo-update && cd ..
```
### 4. Info.plist Configuration
Add the following settings to `ios/Runner/Info.plist` for backfill ads.
* **GADApplicationIdentifier**: App ID for backfill ads
* **SKAdNetworkItems**: Required for ad conversion tracking on iOS 14+
```xml Info.plist theme={null}
GADApplicationIdentifierca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyySKAdNetworkItemsSKAdNetworkIdentifiercstr6suwn9.skadnetworkSKAdNetworkIdentifier4fzdc2evr5.skadnetworkSKAdNetworkIdentifier4pfyvq9l8r.skadnetworkSKAdNetworkIdentifier2fnua5tdw4.skadnetworkSKAdNetworkIdentifierydx93a7ass.skadnetworkSKAdNetworkIdentifier5a6flpkh64.skadnetworkSKAdNetworkIdentifierp78axxw29g.skadnetworkSKAdNetworkIdentifierv72qych5uu.skadnetworkSKAdNetworkIdentifierludvb6z3bs.skadnetworkSKAdNetworkIdentifiercp8zw746q7.skadnetworkSKAdNetworkIdentifierv9wttpbfk9.skadnetworkSKAdNetworkIdentifierlr83yxwka7.skadnetworkSKAdNetworkIdentifier578prtvx9j.skadnetworkSKAdNetworkIdentifier7ug5zh24hu.skadnetworkSKAdNetworkIdentifierwg4vff78zm.skadnetworkSKAdNetworkIdentifiermj797d8u6f.skadnetworkSKAdNetworkIdentifier8s468mfl3y.skadnetworkSKAdNetworkIdentifier4dzt52r2t5.skadnetworkSKAdNetworkIdentifiervhf287vqwu.skadnetworkSKAdNetworkIdentifierc6k4g5qg8m.skadnetworkSKAdNetworkIdentifier5tjdwbrq8w.skadnetworkSKAdNetworkIdentifier294l99pt4k.skadnetworkSKAdNetworkIdentifiermqn7fxpca7.skadnetworkSKAdNetworkIdentifierg6gcrrvk4p.skadnetworkSKAdNetworkIdentifiertl55sbb4fm.skadnetworkSKAdNetworkIdentifiers39g8k73mm.skadnetworkSKAdNetworkIdentifiera2p9lx4jpn.skadnetworkSKAdNetworkIdentifierk6y4y55b64.skadnetworkSKAdNetworkIdentifier488r3q3dtq.skadnetworkSKAdNetworkIdentifier9rd848q2bz.skadnetworkSKAdNetworkIdentifierglqzh8vgby.skadnetworkSKAdNetworkIdentifier5lm9lj6jb7.skadnetworkSKAdNetworkIdentifier97r2b46745.skadnetworkSKAdNetworkIdentifierzmvfpc5aq8.skadnetworkSKAdNetworkIdentifier3sh42y64q3.skadnetworkSKAdNetworkIdentifiermlmmfzh3r3.skadnetworkSKAdNetworkIdentifier3rd42ekr43.skadnetworkSKAdNetworkIdentifier2u9pt9hc89.skadnetworkSKAdNetworkIdentifierm8dbw4sv7c.skadnetworkSKAdNetworkIdentifier238da6jt44.skadnetworkSKAdNetworkIdentifier4468km3ulz.skadnetworkSKAdNetworkIdentifier4w7y6s5ca2.skadnetworkSKAdNetworkIdentifiera8cz6cu7e5.skadnetworkSKAdNetworkIdentifier77y3x8wds4.skadnetworkSKAdNetworkIdentifiern9x2a789qt.skadnetworkSKAdNetworkIdentifierhs6bdukanm.skadnetworkSKAdNetworkIdentifierf7s53z58qe.skadnetworkSKAdNetworkIdentifier9t245vhmpl.skadnetworkSKAdNetworkIdentifiermp6xlyr22a.skadnetworkSKAdNetworkIdentifierprcb7njmu6.skadnetworkSKAdNetworkIdentifierkbd757ywx3.skadnetworkSKAdNetworkIdentifierk674qkevps.skadnetworkSKAdNetworkIdentifiert38b2kh725.skadnetworkSKAdNetworkIdentifierfeyaarzu9v.skadnetworkSKAdNetworkIdentifier5f5u5tfb26.skadnetworkSKAdNetworkIdentifier424m5254lk.skadnetworkSKAdNetworkIdentifiere5fvkxwrpn.skadnetworkSKAdNetworkIdentifier3qy4746246.skadnetworkSKAdNetworkIdentifierklf5c3l5u5.skadnetworkSKAdNetworkIdentifierzq492l623r.skadnetworkSKAdNetworkIdentifier22mmun2rn5.skadnetworkSKAdNetworkIdentifierav6w8kgt66.skadnetworkSKAdNetworkIdentifierv79kvwwj4g.skadnetworkSKAdNetworkIdentifier44jx6755aq.skadnetworkSKAdNetworkIdentifier32z4fx6l9h.skadnetworkSKAdNetworkIdentifierw9q455wk68.skadnetworkSKAdNetworkIdentifierxga6mpmplv.skadnetworkSKAdNetworkIdentifierf38h382jlk.skadnetworkSKAdNetworkIdentifieryclnxrl5pm.skadnetworkSKAdNetworkIdentifierx44k69ngh6.skadnetworkSKAdNetworkIdentifierppxm28t8ap.skadnetworkSKAdNetworkIdentifierf73kdq92p3.skadnetworkSKAdNetworkIdentifier5l3tpt7t6e.skadnetworkSKAdNetworkIdentifierwzmmz9fp6w.skadnetworkSKAdNetworkIdentifierpwa73g5rt2.skadnetworkSKAdNetworkIdentifier9nlqeag3gk.skadnetworkSKAdNetworkIdentifier6yxyv74ff7.skadnetworkSKAdNetworkIdentifieruw77j35x4d.skadnetworkSKAdNetworkIdentifier54nzkqm89y.skadnetworkSKAdNetworkIdentifier44n7hlldy6.skadnetworkSKAdNetworkIdentifier6964rsfnh4.skadnetworkSKAdNetworkIdentifier6g9af3uyq4.skadnetworkSKAdNetworkIdentifier6v7lgmsu45.skadnetworkSKAdNetworkIdentifier7fmhfwg9en.skadnetworkSKAdNetworkIdentifiera7xqa6mtl2.skadnetworkSKAdNetworkIdentifier84993kbrcf.skadnetworkSKAdNetworkIdentifierbxvub5ada5.skadnetworkSKAdNetworkIdentifier79pbpufp6p.skadnetworkSKAdNetworkIdentifier9vvzujtq5s.skadnetworkSKAdNetworkIdentifier7rz58n8ntl.skadnetworkSKAdNetworkIdentifier3qcr597p9d.skadnetworkSKAdNetworkIdentifier737z793b9f.skadnetworkSKAdNetworkIdentifier89z7zv988g.skadnetworkSKAdNetworkIdentifierc3frkrj4fj.skadnetworkSKAdNetworkIdentifier275upjj5gd.skadnetworkSKAdNetworkIdentifier3l6bd9hu43.skadnetworkSKAdNetworkIdentifier52fl2v3hgk.skadnetworkSKAdNetworkIdentifier6p4ks3rnbw.skadnetworkSKAdNetworkIdentifierapzhy3va96.skadnetworkSKAdNetworkIdentifier9b89h5y424.skadnetworkSKAdNetworkIdentifier523jb4fst2.skadnetworkSKAdNetworkIdentifierb9bk5wbcq9.skadnetworkSKAdNetworkIdentifiercg4yq2srnc.skadnetworkSKAdNetworkIdentifiercj5566h2ga.skadnetworkSKAdNetworkIdentifier24zw6aqk47.skadnetworkSKAdNetworkIdentifier74b6s63p6l.skadnetworkSKAdNetworkIdentifier8m87ys6875.skadnetworkSKAdNetworkIdentifier24t9a8vw3c.skadnetworkSKAdNetworkIdentifier6xzpu9s2p8.skadnetworkSKAdNetworkIdentifiercs644xg564.skadnetworkSKAdNetworkIdentifierdbu4b84rxf.skadnetworkSKAdNetworkIdentifierejvt5qm6ak.skadnetworkSKAdNetworkIdentifiermtkv5xtk9e.skadnetworkSKAdNetworkIdentifierkbmxgpxpgc.skadnetworkSKAdNetworkIdentifierhb56zgv37p.skadnetworkSKAdNetworkIdentifierpwdxu55a5a.skadnetworkSKAdNetworkIdentifierr45fhb6rf7.skadnetworkSKAdNetworkIdentifiert6d3zquu66.skadnetworkSKAdNetworkIdentifieru679fj5vs4.skadnetworkSKAdNetworkIdentifierdzg6xy7pwj.skadnetworkSKAdNetworkIdentifierg28c52eehv.skadnetworkSKAdNetworkIdentifiermls7yz5dvl.skadnetworkSKAdNetworkIdentifiervcra2ehyfk.skadnetworkSKAdNetworkIdentifierrx5hdcabgc.skadnetworkSKAdNetworkIdentifierqwpu75vrh2.skadnetworkSKAdNetworkIdentifierrvh3l7un93.skadnetworkSKAdNetworkIdentifierx5l83yy675.skadnetworkSKAdNetworkIdentifierm5mvw97r93.skadnetworkSKAdNetworkIdentifierxy9t38ct57.skadnetworkSKAdNetworkIdentifierg2y4y55b64.skadnetworkSKAdNetworkIdentifiergta9lk7p23.skadnetworkSKAdNetworkIdentifiern6fk4nfna4.skadnetworkSKAdNetworkIdentifierqu637u8glc.skadnetworkSKAdNetworkIdentifiery5ghdn5j9k.skadnetworkSKAdNetworkIdentifiery45688jllp.skadnetworkSKAdNetworkIdentifierggvn48r87g.skadnetworkSKAdNetworkIdentifierm297p6643m.skadnetworkSKAdNetworkIdentifierqqp299437r.skadnetworkSKAdNetworkIdentifierhdw39hrw9y.skadnetworkSKAdNetworkIdentifierkrvm3zuq6h.skadnetworkSKAdNetworkIdentifiercwn433xbcr.skadnetworkSKAdNetworkIdentifierns5j362hk7.skadnetworkSKAdNetworkIdentifiersu67r6k2v3.skadnetworkSKAdNetworkIdentifierz959bm4gru.skadnetworkSKAdNetworkIdentifierx8uqf25wch.skadnetworkSKAdNetworkIdentifiern38lu8286q.skadnetworkSKAdNetworkIdentifierfz2k2k5tej.skadnetworkSKAdNetworkIdentifierecpz2srf59.skadnetworkSKAdNetworkIdentifierbvpn9ufa9b.skadnetworkSKAdNetworkIdentifier6rd35atwn8.skadnetworkSKAdNetworkIdentifierln5gz23vtd.skadnetworkSKAdNetworkIdentifiertmhh9296z4.skadnetworkSKAdNetworkIdentifiersczv5946wb.skadnetworkSKAdNetworkIdentifierdkc879ngq3.skadnetworkSKAdNetworkIdentifier87u5trcl3r.skadnetworkSKAdNetworkIdentifierfq6vru337s.skadnetworkSKAdNetworkIdentifierf2zub97jtl.skadnetworkSKAdNetworkIdentifierthzdn4h5nc.adattributionkitSKAdNetworkIdentifierraa6f494kr.adattributionkitSKAdNetworkIdentifier6lz2ygh3q6.adattributionkitSKAdNetworkIdentifierm2jqnlggk3.adattributionkitSKAdNetworkIdentifierpg7ctvrt6f.adattributionkitSKAdNetworkIdentifier77y3x8wds4.adattributionkitSKAdNetworkIdentifier55644vm79v.skadnetworkSKAdNetworkIdentifiernzq8sh4pbs.skadnetworkSKAdNetworkIdentifier55y65gfgn7.skadnetworkSKAdNetworkIdentifier252b5q8x7y.skadnetworkSKAdNetworkIdentifier47vhws6wlr.skadnetworkSKAdNetworkIdentifier4mn522wn87.skadnetworkSKAdNetworkIdentifier577p5t736z.skadnetworkSKAdNetworkIdentifier7953jerfzd.skadnetworkSKAdNetworkIdentifier7bxrt786m8.skadnetworkSKAdNetworkIdentifier7fbxrn65az.skadnetworkSKAdNetworkIdentifier8c4e2ghe7u.skadnetworkSKAdNetworkIdentifier8r8llnkz5a.skadnetworkSKAdNetworkIdentifier9yg77x724h.skadnetworkSKAdNetworkIdentifierce8ybjwass.skadnetworkSKAdNetworkIdentifierdt3cjx1a9i.skadnetworkSKAdNetworkIdentifiereh6m2bh4zr.skadnetworkSKAdNetworkIdentifiergta8lk7p23.skadnetworkSKAdNetworkIdentifiergvmwg8q7h5.skadnetworkSKAdNetworkIdentifierhjevpa356n.skadnetworkSKAdNetworkIdentifierjk2fsx2rgz.skadnetworkSKAdNetworkIdentifiern66cz3y3bx.skadnetworkSKAdNetworkIdentifierpu4na253f3.skadnetworkSKAdNetworkIdentifierr8lj5b58b5.skadnetworkSKAdNetworkIdentifiers69wq72ugq.skadnetworkSKAdNetworkIdentifierv4nxqhlyqp.skadnetworkSKAdNetworkIdentifiervutu7akeur.skadnetworkSKAdNetworkIdentifierz24wtl6j62.skadnetworkSKAdNetworkIdentifierz4gj7hsk7h.skadnetworkSKAdNetworkIdentifierx8jxxk4ff5.skadnetworkSKAdNetworkIdentifierx8yj322td6.skadnetwork
```
If your Info.plist already contains a `SKAdNetworkItems` key, do not add a new one — merge only the array (``) entries. Duplicate keys cause iOS to use only the first array.
Please contact Adrop for the full SKAdNetworkItems list and App ID.
### 5. Add Configuration File
#### Android
1. Download the **adrop\_service.json** file from the Ad Control Console.
2. Copy the file to the `android/app/src/main/assets/` directory.
```
android/app/src/main/assets/adrop_service.json
```
#### iOS
1. Download the **adrop\_service.json** file from the Ad Control Console.
2. Add the file to the project root in Xcode.
3. Make sure to add the file to all targets.
***
## Initialization
Initialize the SDK at app startup. Typically called in `main.dart`.
```dart main.dart theme={null}
import 'package:flutter/material.dart';
import 'package:adrop_ads_flutter/adrop_ads_flutter.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
// false: test mode, true: production mode
Adrop.initialize(false);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: HomeScreen(),
);
}
}
```
Call `WidgetsFlutterBinding.ensureInitialized()` before `Adrop.initialize()` so that platform channels are ready. `Adrop.initialize()` returns a `Future`, but you can call it without `await`.
### Initialization Options
```dart theme={null}
await Adrop.initialize(
production, // bool: production mode flag
targetCountries: targetCountries, // List?: target country codes (optional)
useInAppBrowser: useInAppBrowser // bool?: iOS in-app browser usage (optional)
);
```
**Parameters**
| Parameter | Type | Required | Description |
| - | - | - | - |
| `production` | `bool` | Y | `true`: production mode, `false`: test mode |
| `targetCountries` | `List` | N | Country codes to show ads (e.g., `['KR', 'US']`) |
| `useInAppBrowser` | `bool` | N | iOS in-app browser usage (default: `false`) |
**Examples**
```dart theme={null}
// Basic initialization (test mode)
await Adrop.initialize(false);
// Production mode + specific country targeting
await Adrop.initialize(true, targetCountries: ['KR', 'JP', 'US']);
// Production mode + iOS in-app browser
await Adrop.initialize(true, useInAppBrowser: true);
```
***
## Theme Settings
Set the ad UI theme.
```dart theme={null}
import 'package:adrop_ads_flutter/adrop_ads_flutter.dart';
// Set theme
await Adrop.setTheme(AdropTheme.auto); // Follow system settings
await Adrop.setTheme(AdropTheme.light); // Light mode
await Adrop.setTheme(AdropTheme.dark); // Dark mode
```
| Theme | Description |
| - | - |
| `AdropTheme.auto` | Automatically switches based on system settings |
| `AdropTheme.light` | Light mode |
| `AdropTheme.dark` | Dark mode |
***
## Set User Identifier
Set a user identifier (UID) for targeted advertising.
```dart theme={null}
import 'package:adrop_ads_flutter/adrop_ads_flutter.dart';
// After user login
await Adrop.setUID('user_123');
// On logout
await Adrop.setUID('');
```
UID is hashed with SHA-256 before transmission. Do not pass personally identifiable information such as email or phone number directly.
***
## Test Unit IDs
Use the following test unit IDs during development and testing.
| Format | Test Unit ID |
| - | - |
| Banner (320x50) | `PUBLIC_TEST_UNIT_ID_320_50` |
| Banner (320x100) | `PUBLIC_TEST_UNIT_ID_320_100` |
| Native | `PUBLIC_TEST_UNIT_ID_NATIVE` |
| Interstitial | `PUBLIC_TEST_UNIT_ID_INTERSTITIAL` |
| Rewarded | `PUBLIC_TEST_UNIT_ID_REWARDED` |
| Popup (Bottom) | `PUBLIC_TEST_UNIT_ID_POPUP_BOTTOM` |
| Popup (Center) | `PUBLIC_TEST_UNIT_ID_POPUP_CENTER` |
| Popup Video (Bottom 16:9) | `PUBLIC_TEST_UNIT_ID_POPUP_BOTTOM_VIDEO_16_9` |
| Popup Video (Bottom 9:16) | `PUBLIC_TEST_UNIT_ID_POPUP_BOTTOM_VIDEO_9_16` |
| Popup Video (Center 16:9) | `PUBLIC_TEST_UNIT_ID_POPUP_CENTER_VIDEO_16_9` |
| Popup Video (Center 9:16) | `PUBLIC_TEST_UNIT_ID_POPUP_CENTER_VIDEO_9_16` |
| Splash | `PUBLIC_TEST_UNIT_ID_SPLASH` |
Be sure to replace with actual unit IDs before production deployment.
***
## Error Codes
Error codes that can occur in the SDK.
| Error Code | Description |
| - | - |
| `network` | Network error |
| `internal` | Internal error |
| `initialize` | SDK initialization error |
| `invalidUnit` | Invalid unit ID |
| `notTargetCountry` | Not a target country |
| `inactive` | Inactive ad |
| `adNoFill` | No ads to display |
| `adLoadDuplicate` | Duplicate load request |
| `adLoading` | Loading in progress |
| `adEmpty` | Empty ad |
| `adShown` | Ad already shown |
| `adHideForToday` | Hidden for today |
| `accountUsageLimitExceeded` | Account usage limit exceeded |
| `adLandscapeUnsupported` | Landscape mode not supported |
| `backfillNoFill` | No backfill ads |
```dart theme={null}
import 'package:adrop_ads_flutter/adrop_ads_flutter.dart';
// Error handling example
if (errorCode == AdropErrorCode.adNoFill) {
debugPrint('No ads available to display.');
}
```
***
## Deprecated Class Migration
The following classes are deprecated and will be removed in a future version. Please migrate to the new classes.
### AdropBanner → AdropBannerView
The `AdropBanner` widget is deprecated. Migrate to `AdropBannerView`.
```dart theme={null}
// ❌ Deprecated
AdropBanner(
unitId: 'YOUR_UNIT_ID',
onAdropBannerCreated: (controller) {
controller.load();
},
onAdReceived: (banner) { },
);
// ✅ Recommended (AdropBannerView)
final bannerView = AdropBannerView(
unitId: 'YOUR_UNIT_ID',
listener: AdropBannerListener(
onAdReceived: (unitId, metadata) { },
onAdFailedToReceive: (unitId, errorCode) { },
),
);
bannerView.load();
```
### AdropNavigatorObserver (Deprecated)
`AdropNavigatorObserver` is deprecated and will be removed in a future version. This class no longer provides any functionality and can be safely removed from your code.
```dart theme={null}
// ❌ Deprecated — can be removed
MaterialApp(
navigatorObservers: [AdropNavigatorObserver()],
);
// ✅ Remove AdropNavigatorObserver
MaterialApp(
// Remove AdropNavigatorObserver from navigatorObservers
);
```
***
## Troubleshooting
### iOS Build Errors
If you encounter Swift version compatibility issues, add the following to the `post_install` block in `ios/Podfile`.
```ruby ios/Podfile theme={null}
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['BUILD_LIBRARY_FOR_DISTRIBUTION'] = 'YES'
end
end
end
```
### Android Build Errors
If the Kotlin version doesn't match, check the Kotlin version in `android/build.gradle`.
```gradle android/build.gradle theme={null}
buildscript {
ext.kotlin_version = '2.1.0'
}
```
***
## Next Steps
Implement banner ads
Implement native ads
Implement interstitial ads
Implement rewarded ads
Display web ads in WebView
GDPR/CCPA consent management
# Banner Ads
Source: https://docs.adrop.io/sdk/ios/banner
Guide to implementing banner ads in iOS apps.
## Overview
Banner ads are rectangular ads displayed in a portion of the screen. They can be used in both UIKit and SwiftUI.
### Key Features
* Can be fixed at the top, bottom, or middle of the screen
* Supports both image and video ads
* Supports both UIKit and SwiftUI
* Ad event handling through delegates
Use test unit ID in development environment: `PUBLIC_TEST_UNIT_ID_320_100`
***
## UIKit Implementation
In UIKit environment, implement banner ads using the `AdropBanner` class.
### Basic Implementation
```swift theme={null}
import UIKit
import AdropAds
class ViewController: UIViewController {
private var banner: AdropBanner?
override func viewDidLoad() {
super.viewDidLoad()
// 1. Create banner instance
banner = AdropBanner(unitId: "YOUR_UNIT_ID")
// 2. Set delegate
banner?.delegate = self
// 3. Add to view hierarchy
if let bannerView = banner {
view.addSubview(bannerView)
// 4. Setup Auto Layout
bannerView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
bannerView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
bannerView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
bannerView.widthAnchor.constraint(equalToConstant: 320),
bannerView.heightAnchor.constraint(equalToConstant: 100)
])
}
// 5. Load ad
banner?.load()
}
deinit {
// 6. Release banner resources before deallocation
banner?.destroy()
banner = nil
}
}
// MARK: - AdropBannerDelegate
extension ViewController: AdropBannerDelegate {
func onAdReceived(_ banner: AdropBanner) {
print("Banner ad received successfully")
}
func onAdFailedToReceive(_ banner: AdropBanner, _ errorCode: AdropErrorCode) {
print("Banner ad failed to receive: \(errorCode)")
}
func onAdImpression(_ banner: AdropBanner) {
print("Banner ad impression")
}
func onAdClicked(_ banner: AdropBanner) {
print("Banner ad clicked")
}
}
```
### AdropBanner Initialization
Unit ID created in Ad Control Console
```swift theme={null}
let banner = AdropBanner(unitId: "YOUR_UNIT_ID")
```
### Load Ad
Call the `load()` method to request an ad after adding the banner to the screen.
```swift theme={null}
banner?.load()
```
Call `load()` when the banner is visible on screen. Loading when not visible may result in inaccurate impression measurements.
### Set Context ID
You can set a Context ID for [contextual targeting](/sdk/ios/targeting#contextual-targeting).
```swift theme={null}
// contextId is read-only — set via initializer
let banner = AdropBanner(unitId: "YOUR_UNIT_ID", contextId: "sport")
```
***
## SwiftUI Implementation
In SwiftUI environment, implement banner ads using `AdropBannerRepresented`.
### Basic Implementation
```swift theme={null}
import SwiftUI
import AdropAds
struct ContentView: View {
@State private var represented = AdropBannerRepresented(unitId: "YOUR_UNIT_ID")
var body: some View {
VStack {
Spacer()
Text("Main Content")
Spacer()
// Banner ad
represented
.frame(width: 320, height: 100)
.onAppear {
represented.banner.load()
}
}
}
}
```
### Delegate Handling
You can handle ad events through delegates.
```swift theme={null}
import SwiftUI
import AdropAds
struct ContentView: View {
@StateObject private var bannerDelegate = BannerDelegate()
@State private var represented = AdropBannerRepresented(unitId: "YOUR_UNIT_ID")
var body: some View {
VStack {
Spacer()
if bannerDelegate.isLoaded {
Text("Ad loaded successfully")
}
represented
.frame(width: 320, height: 100)
.onAppear {
represented.delegate = bannerDelegate
represented.banner.load()
}
}
}
}
// Delegate implementation
class BannerDelegate: NSObject, ObservableObject, AdropBannerDelegate {
@Published var isLoaded = false
func onAdReceived(_ banner: AdropBanner) {
print("Banner ad received successfully")
isLoaded = true
}
func onAdFailedToReceive(_ banner: AdropBanner, _ errorCode: AdropErrorCode) {
print("Banner ad failed to receive: \(errorCode)")
isLoaded = false
}
func onAdImpression(_ banner: AdropBanner) {
print("Banner ad impression")
}
func onAdClicked(_ banner: AdropBanner) {
print("Banner ad clicked")
}
}
```
### Set Context ID
```swift theme={null}
AdropBannerRepresented(
unitId: "YOUR_UNIT_ID",
contextId: "sport"
)
.frame(width: 320, height: 100)
```
***
## Objective-C Implementation
How to implement banner ads in Objective-C environment.
### Basic Implementation
```objc theme={null}
@import AdropAds;
@interface ViewController ()
@property (nonatomic, strong) AdropBanner *banner;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 1. Create banner instance
self.banner = [[AdropBanner alloc] initWithUnitId:@"YOUR_UNIT_ID"];
// 2. Set delegate
self.banner.delegate = self;
// 3. Add to view hierarchy
[self.view addSubview:self.banner];
// 4. Setup Auto Layout
self.banner.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[self.banner.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
[self.banner.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor],
[self.banner.widthAnchor constraintEqualToConstant:320],
[self.banner.heightAnchor constraintEqualToConstant:100]
]];
// 5. Load ad
[self.banner load];
}
- (void)dealloc {
[self.banner destroy];
self.banner = nil;
}
#pragma mark - AdropBannerDelegate
- (void)onAdReceived:(AdropBanner *)banner {
NSLog(@"Banner ad received successfully");
}
- (void)onAdFailedToReceive:(AdropBanner *)banner :(AdropErrorCode)errorCode {
NSLog(@"Banner ad failed to receive: %ld", (long)errorCode);
}
- (void)onAdImpression:(AdropBanner *)banner {
NSLog(@"Banner ad impression");
}
- (void)onAdClicked:(AdropBanner *)banner {
NSLog(@"Banner ad clicked");
}
@end
```
### Set Context ID
```objc theme={null}
// contextId is read-only — set via initializer
AdropBanner *banner = [[AdropBanner alloc] initWithUnitId:@"YOUR_UNIT_ID" contextId:@"sport"];
```
***
## Delegate Methods
The `AdropBannerDelegate` protocol handles ad lifecycle events.
### onAdReceived (Required)
Called when ad is received successfully.
```swift theme={null}
func onAdReceived(_ banner: AdropBanner) {
print("Banner ad received successfully")
// Handle ad loading indicator, etc.
}
```
### onAdFailedToReceive (Required)
Called when ad fails to load.
```swift theme={null}
func onAdFailedToReceive(_ banner: AdropBanner, _ errorCode: AdropErrorCode) {
print("Banner ad failed to receive: \(errorCode)")
// Handle errors and display alternative content
}
```
Error code indicating the type of error. See [Reference](/sdk/ios/reference#error-codes) for details.
### onAdImpression (Optional)
Called when ad is displayed on screen.
```swift theme={null}
func onAdImpression(_ banner: AdropBanner) {
print("Banner ad impression")
// Handle impression analytics logging, etc.
}
```
### onAdClicked (Optional)
Called when user clicks on the ad.
```swift theme={null}
func onAdClicked(_ banner: AdropBanner) {
print("Banner ad clicked")
// Handle click analytics logging, etc.
}
```
### onAdVideoStart (Optional)
Called when a video ad starts playing.
```swift theme={null}
func onAdVideoStart(_ banner: AdropBanner) {
print("Banner video started")
// Handle video start analytics logging, etc.
}
```
### onAdVideoEnd (Optional)
Called when a video ad finishes playing.
```swift theme={null}
func onAdVideoEnd(_ banner: AdropBanner) {
print("Banner video ended")
// Handle video end analytics logging, etc.
}
```
***
## Closure Callbacks
As an alternative to delegates, you can use closure-based callbacks.
```swift theme={null}
let banner = AdropBanner(unitId: "YOUR_UNIT_ID")
banner.onAdReceived = { banner in
print("Banner ad received")
}
banner.onAdFailedToReceive = { banner, errorCode in
print("Banner ad failed: \(errorCode)")
}
banner.onAdImpression = { banner in
print("Banner ad impression")
}
banner.onAdClicked = { banner in
print("Banner ad clicked")
}
banner.onAdVideoStart = { banner in
print("Banner video started")
}
banner.onAdVideoEnd = { banner in
print("Banner video ended")
}
```
If both a delegate and closures are set, both will be called.
***
## Lifecycle Management
### destroy()
When you no longer need the banner, call `destroy()` to release its resources — WebView, tracking, and any active video playback are torn down actively.
`destroy()` is idempotent, so calling it multiple times is safe. The instance cannot be reused after destroy.
```swift Swift theme={null}
deinit {
banner?.destroy()
banner = nil
}
```
```objc Objective-C theme={null}
- (void)dealloc {
[self.banner destroy];
self.banner = nil;
}
```
Skipping `destroy()` when the view controller deallocates leaves the WebView and related resources uncollected, which can cause memory leaks and performance degradation — especially impactful for video banners.
***
## Ad Sizes
Banner ads require view size to match the size set in the unit.
### Common Banner Sizes
| Size | Use Case |
| - | - |
| 320 x 50 | Small banner |
| 320 x 100 | Large banner |
| 16:9 ratio | Video banner |
### UIKit
```swift theme={null}
bannerView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
bannerView.widthAnchor.constraint(equalToConstant: 320),
bannerView.heightAnchor.constraint(equalToConstant: 100)
])
```
### SwiftUI
```swift theme={null}
represented
.frame(width: 320, height: 100)
.onAppear {
represented.banner.load()
}
```
***
## Best Practices
### 1. Memory Management
Call [`destroy()`](#destroy) when the view controller is deallocated to release banner resources.
```swift theme={null}
deinit {
banner?.destroy()
banner = nil
}
```
### 2. Screen Visibility
Load ads when the banner is visible on screen.
```swift theme={null}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
banner?.load()
}
```
### 3. Reuse the Banner Instance
Keep a single `AdropBanner` instance and call `load()` on it whenever you need a new ad. Do **not** create a new `AdropBanner` for every refresh.
Recreating the banner for each ad request allocates a new `WKWebView` and its associated WebContent process each time. Under frequent refresh conditions — especially with video banners — this accumulates resource usage and can degrade rendering performance.
```swift Recommended (reuse) theme={null}
class ViewController: UIViewController {
private var banner: AdropBanner?
override func viewDidLoad() {
super.viewDidLoad()
banner = AdropBanner(unitId: "YOUR_UNIT_ID")
banner?.delegate = self
// Add to view hierarchy...
banner?.load()
}
func refreshAd() {
// Reuse the existing instance
banner?.load()
}
}
```
```swift Not recommended (recreate) theme={null}
func refreshAd() {
// Do NOT allocate a new AdropBanner on every refresh
banner?.destroy()
banner = AdropBanner(unitId: "YOUR_UNIT_ID")
banner?.delegate = self
// ...add to view hierarchy again
banner?.load()
}
```
`onAdReceived` fires again on each subsequent `load()` call, so your existing delegate handles the new ad automatically.
### 4. Error Handling
Implement appropriate error handling when ad loading fails.
```swift theme={null}
func onAdFailedToReceive(_ banner: AdropBanner, _ errorCode: AdropErrorCode) {
switch errorCode {
case .ERROR_CODE_NETWORK:
print("Network error: Check connection")
case .ERROR_CODE_AD_NO_FILL:
print("No available ads")
default:
print("Ad load failed: \(errorCode)")
}
// Hide ad area or display fallback content
bannerContainerView.isHidden = true
// Or show your own promotional banner
showPromotionBanner()
}
```
***
## Test Unit IDs
Use the following test unit IDs during development and testing.
| Ad Type | Test Unit ID | Size |
| - | - | - |
| Banner (320x50) | `PUBLIC_TEST_UNIT_ID_320_50` | 320 x 50 |
| Banner (320x100) | `PUBLIC_TEST_UNIT_ID_320_100` | 320 x 100 |
| Carousel Banner | `PUBLIC_TEST_UNIT_ID_CAROUSEL` | Variable |
| Banner Video (16:9) | `PUBLIC_TEST_UNIT_ID_BANNER_VIDEO_16_9` | 16:9 ratio |
| Banner Video (9:16) | `PUBLIC_TEST_UNIT_ID_BANNER_VIDEO_9_16` | 9:16 ratio |
### Usage Example
```swift Swift theme={null}
let banner = AdropBanner(unitId: AdropUnitId.PUBLIC_TEST_UNIT_ID_320_100)
```
```objc Objective-C theme={null}
AdropBanner *banner = [[AdropBanner alloc] initWithUnitId:PUBLIC_TEST_UNIT_ID_320_100];
```
***
## Video Playback Control
For video banner ads, you can manually control video playback using `play()` and `pause()`.
### play()
Resumes video playback. Call this when the banner becomes visible again — for example, after a popup is dismissed or the app returns from the background.
```swift Swift theme={null}
banner?.play()
```
```objc Objective-C theme={null}
[self.banner play];
```
### pause()
Pauses video playback. Call this when the banner is hidden by a popup or the app enters the background.
```swift Swift theme={null}
banner?.pause()
```
```objc Objective-C theme={null}
[self.banner pause];
```
### Example: Background/Foreground Handling
```swift theme={null}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
banner?.play()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
banner?.pause()
}
```
`play()` and `pause()` only affect video banner ads. They have no effect on image banners.
***
## Custom Click Handling
You can take control of ad click behavior by using `useCustomClick`. When enabled, the SDK will not automatically open the destination URL on click, and you can handle it yourself.
### useCustomClick
```swift theme={null}
let banner = AdropBanner(unitId: "YOUR_UNIT_ID")
banner.useCustomClick = true
banner.delegate = self
banner.load()
```
When `useCustomClick` is `true`, the SDK will not open the destination URL automatically when the ad is clicked. The `onAdClicked` delegate callback will still fire, and you can use the `open()` method or handle navigation yourself.
### open()
Opens the destination URL of the ad. Can optionally pass a custom URL.
```swift theme={null}
// Open the ad's default destination URL
banner?.open()
// Open a custom URL
banner?.open("https://example.com")
// Open using in-app browser
banner?.open(nil, useInAppBrowser: true)
```
***
## Batch Loading (loads)
Use `AdropBanner.loads(...)` to request multiple banner ads in a single batched call. This is useful for building carousels, paginated feeds, or prefetching a pool of ads.
### Signature
```swift Swift theme={null}
AdropBanner.loads(
unitId: String,
contextId: String = "",
delegate: AdropBannerDelegate
)
```
```objc Objective-C theme={null}
[AdropBanner loadsWithUnitId:@"YOUR_UNIT_ID"
contextId:@""
delegate:delegate];
```
### Usage
```swift Swift theme={null}
class ViewController: UIViewController {
private var banners: [AdropBanner] = []
override func viewDidLoad() {
super.viewDidLoad()
AdropBanner.loads(unitId: "YOUR_UNIT_ID", delegate: self)
}
}
extension ViewController: AdropBannerDelegate {
// Required single-load callbacks — unused on the batch path.
func onAdReceived(_ banner: AdropBanner) { /* batch path */ }
func onAdFailedToReceive(_ banner: AdropBanner, _ errorCode: AdropErrorCode) {}
// Batch success — deliver up to 5 ready-to-use banners.
func onAdsReceived(_ banners: [AdropBanner]) {
self.banners = banners
banners.forEach { banner in
// Attach to your view hierarchy (UIScrollView, UIPageViewController, ...)
stackView.addArrangedSubview(banner)
}
}
// Batch failure
func onAdsFailedToReceive(_ errorCode: AdropErrorCode) {
print("Batch load failed: \(errorCode)")
}
// Click / impression / video callbacks still fire per instance.
func onAdClicked(_ banner: AdropBanner) {
print("Banner clicked")
}
func onAdImpression(_ banner: AdropBanner) {
print("Banner impression")
}
}
```
```objc Objective-C theme={null}
@interface ViewController ()
@property (nonatomic, strong) NSArray *banners;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[AdropBanner loadsWithUnitId:@"YOUR_UNIT_ID" contextId:@"" delegate:self];
}
#pragma mark - AdropBannerDelegate
- (void)onAdReceived:(AdropBanner *)banner { /* batch path */ }
- (void)onAdFailedToReceive:(AdropBanner *)banner :(AdropErrorCode)errorCode {}
- (void)onAdsReceived:(NSArray *)banners {
self.banners = banners;
for (AdropBanner *banner in banners) {
[self.stackView addArrangedSubview:banner];
}
}
- (void)onAdsFailedToReceive:(AdropErrorCode)errorCode {
NSLog(@"Batch load failed: %ld", (long)errorCode);
}
- (void)onAdClicked:(AdropBanner *)banner {
NSLog(@"Banner clicked");
}
- (void)onAdImpression:(AdropBanner *)banner {
NSLog(@"Banner impression");
}
@end
```
### Delegate Methods
| Method | When it fires |
| - | - |
| `onAdsReceived(_:)` | Batch delivered. Each `AdropBanner` already has the delegate attached and ad data applied. |
| `onAdsFailedToReceive(_:)` | Request rejected, network failure, or no fillable ads were returned (`ERROR_CODE_AD_NO_FILL`). |
| `onAdClicked` / `onAdImpression` / `onAdVideoStart` / `onAdVideoEnd` | Fire on each returned banner just like a single `load()`. |
The singular `onAdReceived(_:)` / `onAdFailedToReceive(_:_:)` callbacks are **not** invoked on the `loads` path. Use `onAdsReceived(_:)` / `onAdsFailedToReceive(_:)` as the sole batch signals.
### Constants
Upper bound on ads returned from a single `loads(...)` call.
### Constraints
* **Maximum 5 ads per call.** If the server returns more, only the first 5 are delivered.
* **Backfill is not applied.** If no direct ads fill, `onAdsFailedToReceive` is called with `ERROR_CODE_AD_NO_FILL` regardless of backfill configuration.
* **Do not call [`destroy()`](#destroy) inside `onAdsReceived`.** Attach each banner to the view hierarchy first, then call `destroy()` when the view controller deallocates.
* `onAdsReceived` fires as soon as ad data is applied, matching the single `load()` contract — the creative paints shortly after you add the banner to a view hierarchy.
* Hold strong references to the returned banners (e.g., store them on the view controller). If they deallocate before being attached, they're silently dropped.
***
## Backfill Ads
When backfill ads are enabled, backfill ads are automatically loaded when direct ads are unavailable. Use the `isBackfilled` property to check if the ad is a backfill ad.
```swift Swift theme={null}
class ViewController: UIViewController, AdropBannerDelegate {
private var banner: AdropBanner?
func onAdReceived(_ banner: AdropBanner) {
if banner.isBackfilled {
print("Backfill ad loaded")
} else {
print("Direct ad loaded")
}
}
func onAdFailedToReceive(_ banner: AdropBanner, _ errorCode: AdropErrorCode) {
switch errorCode {
case .ERROR_CODE_AD_NO_FILL:
print("No direct ad, requesting backfill...")
case .ERROR_CODE_AD_BACKFILL_NO_FILL:
print("No backfill ad available")
// Hide ad area
banner.isHidden = true
default:
print("Ad load failed: \(errorCode)")
}
}
}
```
```objc Objective-C theme={null}
@interface ViewController ()
@property (nonatomic, strong) AdropBanner *banner;
@end
@implementation ViewController
- (void)onAdReceived:(AdropBanner *)banner {
if (banner.isBackfilled) {
NSLog(@"Backfill ad loaded");
} else {
NSLog(@"Direct ad loaded");
}
}
- (void)onAdFailedToReceive:(AdropBanner *)banner :(AdropErrorCode)errorCode {
if (errorCode == AdropErrorCodeAdNoFill) {
NSLog(@"No direct ad, requesting backfill...");
} else if (errorCode == AdropErrorCodeBackfillNoFill) {
NSLog(@"No backfill ad available");
// Hide ad area
banner.hidden = YES;
} else {
NSLog(@"Ad load failed: %ld", (long)errorCode);
}
}
@end
```
To use backfill ads, add the `AdropAds-Backfill` dependency. See [Getting Started](/sdk/ios/overview).
***
## Next Steps
Implementing customizable native ads
Implementing full-screen interstitial ads
Setting up user attributes and contextual targeting
Classes, delegates, error codes reference
# Examples
Source: https://docs.adrop.io/sdk/ios/examples
Check out integration methods and basic usage of the iOS SDK with real code examples.
## GitHub Repository
iOS SDK example project
***
## Example Project Structure
We provide examples in three different approaches: SwiftUI, UIKit, and Objective-C.
Available in the `adrop-ads-example-ios-swiftUI/` folder.
This is an example implementing ads with SwiftUI-based declarative UI.
Available in the `adrop-ads-example-ios/` folder.
This is an example implementing ads with UIKit-based traditional approach.
Available in the `adrop-ads-example-ios-objective-c/` folder.
This is an example implementing ads with Objective-C.
***
## How to Run Examples
```bash theme={null}
git clone https://github.com/OpenRhapsody/adrop-ads-example-ios.git
cd adrop-ads-example-ios
```
```bash theme={null}
pod install
```
```bash theme={null}
open adrop-ads-example-ios.xcworkspace
```
You must open `.xcworkspace`, not `.xcodeproj`.
1. Select a simulator or real device
2. Click **Run** (⌘R)
The example project uses test unit IDs, so you can run it immediately without any additional configuration.
***
## Key Example Code
### Banner Ad (SwiftUI)
```swift theme={null}
import SwiftUI
import AdropAds
struct BannerExample: View {
var body: some View {
AdropBannerRepresented(unitId: "PUBLIC_TEST_UNIT_ID_320_50")
.frame(width: 320, height: 50)
}
}
```
### Interstitial Ad (UIKit)
```swift theme={null}
import UIKit
import AdropAds
class InterstitialViewController: UIViewController, AdropInterstitialAdDelegate {
var interstitialAd: AdropInterstitialAd?
override func viewDidLoad() {
super.viewDidLoad()
interstitialAd = AdropInterstitialAd(unitId: "PUBLIC_TEST_UNIT_ID_INTERSTITIAL")
interstitialAd?.delegate = self
interstitialAd?.load()
}
func onAdReceived(_ ad: AdropInterstitialAd) {
if ad.isLoaded {
ad.show(fromRootViewController: self)
}
}
func onAdFailedToReceive(_ ad: AdropInterstitialAd, _ errorCode: AdropErrorCode) {
print("Failed: \(errorCode)")
}
}
```
### Interstitial Ad (Objective-C)
```objc theme={null}
@import AdropAds;
@interface InterstitialViewController ()
@property (nonatomic, strong) AdropInterstitialAd *interstitialAd;
@end
@implementation InterstitialViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.interstitialAd = [[AdropInterstitialAd alloc] initWithUnitId:@"PUBLIC_TEST_UNIT_ID_INTERSTITIAL"];
self.interstitialAd.delegate = self;
[self.interstitialAd load];
}
- (void)onAdReceived:(AdropInterstitialAd *)ad {
if (ad.isLoaded) {
[ad showFromRootViewController:self];
}
}
- (void)onAdFailedToReceive:(AdropInterstitialAd *)ad :(AdropErrorCode)errorCode {
NSLog(@"Failed: %ld", (long)errorCode);
}
@end
```
***
### User Event Tracking
```swift theme={null}
import AdropAds
class EventTrackingExample {
func trackEvents() {
// Simple event - no parameters
AdropMetrics.sendEvent(name: "app_open")
// Event with parameters
AdropMetrics.sendEvent(name: "view_item", params: [
"item_id": "SKU-123",
"item_name": "Widget",
"item_category": "Electronics",
"price": 29.99
])
// Multi-item event (purchase)
AdropMetrics.sendEvent(name: "purchase", params: [
"tx_id": "tx_123",
"currency": "KRW",
"items": [
["item_id": "A", "item_name": "Product A", "price": 100, "quantity": 1],
["item_id": "B", "item_name": "Product B", "price": 200, "quantity": 2]
]
])
// Sign up event
AdropMetrics.sendEvent(name: "sign_up", params: [
"method": "google"
])
}
}
```
***
## Related Documentation
SDK installation and initialization
Banner ad implementation
Interstitial ad implementation
API reference
# Interstitial Ads
Source: https://docs.adrop.io/sdk/ios/interstitial
Guide to implementing interstitial ads in iOS apps.
## Overview
Interstitial ads are ads displayed in full-screen format covering the entire app screen. They are suitable for display at natural transition points in the app, such as game level transitions or content page changes.
### Features
* Immersive ads covering the entire screen
* Maintained until user explicitly closes
* Supports both image and video ads
* High visual attention
Use test unit ID in development environment: `PUBLIC_TEST_UNIT_ID_INTERSTITIAL`
***
## Implementation Steps
Implement interstitial ads in 4 steps:
1. **Initialize** - Create AdropInterstitialAd instance
2. **Set Delegate** - Set delegate to receive ad events
3. **Load Ad** - Request and receive ad
4. **Show Ad** - Display ad on screen
***
## UIKit Implementation
### Basic Implementation
```swift Swift theme={null}
import AdropAds
class ViewController: UIViewController {
private var interstitialAd: AdropInterstitialAd?
override func viewDidLoad() {
super.viewDidLoad()
loadInterstitialAd()
}
// 1. Initialize and load ad
private func loadInterstitialAd() {
interstitialAd = AdropInterstitialAd(unitId: "YOUR_UNIT_ID")
interstitialAd?.delegate = self
interstitialAd?.load()
}
// 2. Show ad
private func showInterstitialAd() {
guard let interstitialAd = interstitialAd,
interstitialAd.isLoaded else { return }
interstitialAd.show(fromRootViewController: self)
}
}
```
```objc Objective-C theme={null}
@import AdropAds;
@interface ViewController ()
@property (nonatomic, strong) AdropInterstitialAd *interstitialAd;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self loadInterstitialAd];
}
// 1. Initialize and load ad
- (void)loadInterstitialAd {
self.interstitialAd = [[AdropInterstitialAd alloc] initWithUnitId:@"YOUR_UNIT_ID"];
self.interstitialAd.delegate = self;
[self.interstitialAd load];
}
// 2. Show ad
- (void)showInterstitialAd {
if (self.interstitialAd && self.interstitialAd.isLoaded) {
[self.interstitialAd showFromRootViewController:self];
}
}
@end
```
### isLoaded Property
Property to check if the ad is loaded. It's recommended to check this value before calling `show()`.
```swift Swift theme={null}
if interstitialAd?.isLoaded == true {
interstitialAd?.show(fromRootViewController: self)
}
```
```objc Objective-C theme={null}
if (self.interstitialAd.isLoaded) {
[self.interstitialAd showFromRootViewController:self];
}
```
### Delegate Implementation
```swift Swift theme={null}
extension ViewController: AdropInterstitialAdDelegate {
// Ad received successfully (required)
func onAdReceived(_ ad: AdropInterstitialAd) {
print("Interstitial ad received")
// Show ad when ready
showInterstitialAd()
}
// Ad receive failed (required)
func onAdFailedToReceive(_ ad: AdropInterstitialAd, _ errorCode: AdropErrorCode) {
print("Interstitial ad failed to receive: \(errorCode)")
}
// Ad impression (optional)
func onAdImpression(_ ad: AdropInterstitialAd) {
print("Interstitial ad impression")
}
// Ad clicked (optional)
func onAdClicked(_ ad: AdropInterstitialAd) {
print("Interstitial ad clicked")
}
// Before showing full screen ad (optional)
func onAdWillPresentFullScreen(_ ad: AdropInterstitialAd) {
print("About to show interstitial ad")
}
// After showing full screen ad (optional)
func onAdDidPresentFullScreen(_ ad: AdropInterstitialAd) {
print("Interstitial ad shown")
}
// Before dismissing full screen ad (optional)
func onAdWillDismissFullScreen(_ ad: AdropInterstitialAd) {
print("About to dismiss interstitial ad")
}
// After dismissing full screen ad (optional)
func onAdDidDismissFullScreen(_ ad: AdropInterstitialAd) {
print("Interstitial ad dismissed")
// Preload next ad
loadInterstitialAd()
}
// Full screen ad show failed (optional)
func onAdFailedToShowFullScreen(_ ad: AdropInterstitialAd, _ errorCode: AdropErrorCode) {
print("Interstitial ad failed to show: \(errorCode)")
}
}
```
```objc Objective-C theme={null}
#pragma mark - AdropInterstitialAdDelegate
// Ad received successfully (required)
- (void)onAdReceived:(AdropInterstitialAd *)ad {
NSLog(@"Interstitial ad received");
// Show ad when ready
[self showInterstitialAd];
}
// Ad receive failed (required)
- (void)onAdFailedToReceive:(AdropInterstitialAd *)ad :(AdropErrorCode)errorCode {
NSLog(@"Interstitial ad failed to receive: %ld", (long)errorCode);
}
// Ad impression (optional)
- (void)onAdImpression:(AdropInterstitialAd *)ad {
NSLog(@"Interstitial ad impression");
}
// Ad clicked (optional)
- (void)onAdClicked:(AdropInterstitialAd *)ad {
NSLog(@"Interstitial ad clicked");
}
// Before showing full screen ad (optional)
- (void)onAdWillPresentFullScreen:(AdropInterstitialAd *)ad {
NSLog(@"About to show interstitial ad");
}
// After showing full screen ad (optional)
- (void)onAdDidPresentFullScreen:(AdropInterstitialAd *)ad {
NSLog(@"Interstitial ad shown");
}
// Before dismissing full screen ad (optional)
- (void)onAdWillDismissFullScreen:(AdropInterstitialAd *)ad {
NSLog(@"About to dismiss interstitial ad");
}
// After dismissing full screen ad (optional)
- (void)onAdDidDismissFullScreen:(AdropInterstitialAd *)ad {
NSLog(@"Interstitial ad dismissed");
// Preload next ad
[self loadInterstitialAd];
}
// Full screen ad show failed (optional)
- (void)onAdFailedToShowFullScreen:(AdropInterstitialAd *)ad :(AdropErrorCode)errorCode {
NSLog(@"Interstitial ad failed to show: %ld", (long)errorCode);
}
```
***
## SwiftUI Implementation
In SwiftUI, you can use `UIViewControllerRepresentable` or get the rootViewController from UIWindow to display.
### Method 1: ViewModel Pattern
```swift theme={null}
import SwiftUI
import AdropAds
// ViewModel
class InterstitialAdViewModel: NSObject, ObservableObject {
@Published var isAdReady = false
@Published var isAdShowing = false
private var interstitialAd: AdropInterstitialAd?
init() {
loadAd()
}
func loadAd() {
interstitialAd = AdropInterstitialAd(unitId: "YOUR_UNIT_ID")
interstitialAd?.delegate = self
interstitialAd?.load()
}
func showAd() {
guard let interstitialAd = interstitialAd,
let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let rootViewController = windowScene.windows.first?.rootViewController else {
return
}
interstitialAd.show(fromRootViewController: rootViewController)
}
}
// Delegate
extension InterstitialAdViewModel: AdropInterstitialAdDelegate {
func onAdReceived(_ ad: AdropInterstitialAd) {
DispatchQueue.main.async {
self.isAdReady = true
}
}
func onAdFailedToReceive(_ ad: AdropInterstitialAd, _ errorCode: AdropErrorCode) {
print("Ad receive failed: \(errorCode)")
}
func onAdDidPresentFullScreen(_ ad: AdropInterstitialAd) {
DispatchQueue.main.async {
self.isAdShowing = true
}
}
func onAdDidDismissFullScreen(_ ad: AdropInterstitialAd) {
DispatchQueue.main.async {
self.isAdShowing = false
self.isAdReady = false
}
// Preload next ad
loadAd()
}
}
// View
struct ContentView: View {
@StateObject private var adViewModel = InterstitialAdViewModel()
var body: some View {
VStack {
Text("Interstitial Ad Example")
.font(.title)
Button("Show Interstitial Ad") {
adViewModel.showAd()
}
.disabled(!adViewModel.isAdReady)
.padding()
}
}
}
```
***
## Delegate Methods
### Required Methods
Called when ad is received successfully. You can call `show()` at this point to display the ad.
Called when ad fails to load. You can check the cause of failure through the error code.
### Optional Methods
Called when ad impression is recorded.
Called when user clicks the ad.
Called just before the interstitial ad is displayed. You can pause animations, etc.
Called immediately after the interstitial ad is displayed on screen.
Called just before the interstitial ad is dismissed.
Called immediately after the interstitial ad is dismissed. Good time to preload the next ad.
Called when ad fails to show. You can check the cause of failure through the error code.
***
## Closure Callbacks
As an alternative to delegates, you can use closure-based callbacks.
```swift theme={null}
let interstitialAd = AdropInterstitialAd(unitId: "YOUR_UNIT_ID")
// Required
interstitialAd.onAdReceived = { ad in
print("Ad received")
}
interstitialAd.onAdFailedToReceive = { ad, errorCode in
print("Ad failed: \(errorCode)")
}
// Optional
interstitialAd.onAdImpression = { ad in print("Ad impression") }
interstitialAd.onAdClicked = { ad in print("Ad clicked") }
interstitialAd.onAdWillPresentFullScreen = { ad in print("Ad will present") }
interstitialAd.onAdDidPresentFullScreen = { ad in print("Ad did present") }
interstitialAd.onAdWillDismissFullScreen = { ad in print("Ad will dismiss") }
interstitialAd.onAdDidDismissFullScreen = { ad in print("Ad did dismiss") }
interstitialAd.onAdFailedToShowFullScreen = { ad, errorCode in print("Ad failed to show: \(errorCode)") }
```
If both a delegate and closures are set, both will be called.
***
## Best Practices
### 1. Preload Ads
Preload ads before displaying them to improve user experience.
```swift theme={null}
class GameViewController: UIViewController {
private var interstitialAd: AdropInterstitialAd?
private var isAdReady = false
override func viewDidLoad() {
super.viewDidLoad()
// Preload when entering screen
preloadInterstitialAd()
}
private func preloadInterstitialAd() {
interstitialAd = AdropInterstitialAd(unitId: "YOUR_UNIT_ID")
interstitialAd?.delegate = self
interstitialAd?.load()
}
func onGameLevelComplete() {
// Show immediately when level completes
if isAdReady {
interstitialAd?.show(fromRootViewController: self)
}
}
}
extension GameViewController: AdropInterstitialAdDelegate {
func onAdReceived(_ ad: AdropInterstitialAd) {
isAdReady = true
}
func onAdFailedToReceive(_ ad: AdropInterstitialAd, _ errorCode: AdropErrorCode) {
print("Ad load failed: \(errorCode)")
}
func onAdDidDismissFullScreen(_ ad: AdropInterstitialAd) {
isAdReady = false
// Preload next ad
preloadInterstitialAd()
}
}
```
### 2. Appropriate Display Timing
Display ads at natural transition points in your app.
```swift theme={null}
// Good example: Game level transition
func onLevelComplete() {
saveProgress()
showInterstitialAd()
loadNextLevel()
}
// Good example: Content reading complete
func onArticleFinished() {
showInterstitialAd()
}
// Bad example: During user action
func onButtonTap() {
showInterstitialAd() // Disrupts user experience
performAction()
}
```
### 3. Reload After Dismissal
Preload the next ad after the ad is dismissed.
```swift theme={null}
func onAdDidDismissFullScreen(_ ad: AdropInterstitialAd) {
// Immediately load next ad
loadInterstitialAd()
}
```
### 4. Error Handling
Implement error handling for ad load failures.
```swift theme={null}
func onAdFailedToReceive(_ ad: AdropInterstitialAd, _ errorCode: AdropErrorCode) {
switch errorCode {
case .ERROR_CODE_NETWORK:
print("Network error: Retry later")
retryAfterDelay()
case .ERROR_CODE_AD_NO_FILL:
print("No available ads")
continueWithoutAd()
default:
print("Ad load failed: \(errorCode)")
}
}
private func retryAfterDelay() {
DispatchQueue.main.asyncAfter(deadline: .now() + 30) {
self.loadInterstitialAd()
}
}
```
### 5. Frequency Capping
Limit how often ads are displayed to avoid being too intrusive.
```swift theme={null}
class AdFrequencyManager {
private var lastAdShownTime: Date?
private let minimumInterval: TimeInterval = 180 // 3 minutes
func canShowAd() -> Bool {
guard let lastTime = lastAdShownTime else {
return true
}
return Date().timeIntervalSince(lastTime) >= minimumInterval
}
func recordAdShown() {
lastAdShownTime = Date()
}
}
// Usage example
let frequencyManager = AdFrequencyManager()
func showInterstitialIfAllowed() {
guard frequencyManager.canShowAd() else {
print("Ad display interval too short")
return
}
interstitialAd?.show(fromRootViewController: self)
}
func onAdDidPresentFullScreen(_ ad: AdropInterstitialAd) {
frequencyManager.recordAdShown()
}
```
***
## Next Steps
Improve user engagement with rewarded ads
Implementing banner ads
User attributes and contextual targeting
API reference documentation
# Native Ads
Source: https://docs.adrop.io/sdk/ios/native
Guide to implementing native ads in iOS.
## Overview
Native ads are ad formats that naturally integrate with your app's UI. You can freely customize ad elements (title, image, description, etc.) to match your app design.
### Key Features
* Custom design that perfectly integrates with app UI
* Individual access and placement of ad elements
* Option to display advertiser profile information
* Support for additional custom fields
***
## Implementation Steps
Native ad implementation proceeds in the following steps:
1. Load ad
2. Create custom view
3. Bind view
4. Handle delegates
***
## 1. Load Ad
Create an `AdropNativeAd` instance and load the ad.
```swift Swift theme={null}
import AdropAds
class MyViewController: UIViewController {
private var nativeAd: AdropNativeAd?
override func viewDidLoad() {
super.viewDidLoad()
// Create native ad
nativeAd = AdropNativeAd(unitId: "YOUR_NATIVE_UNIT_ID")
nativeAd?.delegate = self
// Load ad
nativeAd?.load()
}
}
```
```objc Objective-C theme={null}
@import AdropAds;
@interface MyViewController ()
@property (nonatomic, strong) AdropNativeAd *nativeAd;
@end
@implementation MyViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Create native ad
self.nativeAd = [[AdropNativeAd alloc] initWithUnitId:@"YOUR_NATIVE_UNIT_ID"];
self.nativeAd.delegate = self;
// Load ad
[self.nativeAd load];
}
@end
```
### Set Context ID
You can set a Context ID for [contextual targeting](/sdk/ios/targeting#contextual-targeting).
```swift Swift theme={null}
// contextId is read-only — set via initializer
let nativeAd = AdropNativeAd(unitId: "YOUR_UNIT_ID", contextId: "tech")
```
```objc Objective-C theme={null}
// contextId is read-only — set via initializer
AdropNativeAd *nativeAd = [[AdropNativeAd alloc] initWithUnitId:@"YOUR_UNIT_ID" contextId:@"tech"];
```
Use test unit IDs during development. See the [Test Unit IDs](#test-unit-ids) section.
***
## 2. Create Custom View
Compose your native ad view using Storyboard or code.
### UIKit (Storyboard/XIB)
Create `AdropNativeAdView` in Storyboard and connect ad elements as IBOutlets.
```swift theme={null}
import AdropAds
class NativeAdViewCell: UITableViewCell {
@IBOutlet weak var nativeAdView: AdropNativeAdView!
@IBOutlet weak var iconImageView: UIImageView!
@IBOutlet weak var headlineLabel: UILabel!
@IBOutlet weak var bodyLabel: UILabel!
@IBOutlet weak var mediaView: UIView!
@IBOutlet weak var callToActionButton: UIButton!
// Advertiser profile (optional)
@IBOutlet weak var advertiserLogoImageView: UIImageView!
@IBOutlet weak var advertiserNameLabel: UILabel!
func configure(with ad: AdropNativeAd) {
// View binding (see next section)
}
}
```
### UIKit (Code)
```swift theme={null}
import AdropAds
class NativeAdView: UIView {
let nativeAdView = AdropNativeAdView()
let iconImageView = UIImageView()
let headlineLabel = UILabel()
let bodyLabel = UILabel()
let mediaView = UIView()
let callToActionButton = UIButton()
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
private func setupViews() {
addSubview(nativeAdView)
nativeAdView.addSubview(iconImageView)
nativeAdView.addSubview(headlineLabel)
nativeAdView.addSubview(bodyLabel)
nativeAdView.addSubview(mediaView)
nativeAdView.addSubview(callToActionButton)
// Auto Layout setup
// ...
}
}
```
***
## 3. Bind View
Bind ad elements to `AdropNativeAdView` to display ad data.
```swift theme={null}
func configure(with ad: AdropNativeAd) {
// Bind each element to AdropNativeAdView
nativeAdView.setIconView(iconImageView)
nativeAdView.setHeadLineView(headlineLabel)
nativeAdView.setBodyView(bodyLabel)
nativeAdView.setMediaView(mediaView)
nativeAdView.setCallToActionView(callToActionButton)
// Advertiser profile (optional)
if let advertiserLogoView = advertiserLogoImageView,
let advertiserNameView = advertiserNameLabel {
nativeAdView.setProfileLogoView(advertiserLogoView)
nativeAdView.setProfileNameView(advertiserNameView)
}
// Set ad data (automatically displayed in bound views)
nativeAdView.setNativeAd(ad)
}
```
### Binding Methods
| Method | Description | Required |
| - | - | - |
| `setIconView(_:onClick:)` | Set icon image view. `onClick: ((AdropNativeAd?, UIView) -> Void)? = nil` | Optional |
| `setHeadLineView(_:onClick:)` | Set title label. `onClick: ((AdropNativeAd?, UIView) -> Void)? = nil` | Optional |
| `setBodyView(_:)` | Set description label | Optional |
| `setMediaView(_:)` | Set main image/video view. **Required for video creatives** — VTR and `onAdVideoStart` / `onAdVideoEnd` only fire when the SDK's media surface is bound here. See [Video Tracking and VTR](#video-tracking-and-vtr). | Optional |
| `setCallToActionView(_:onClick:)` | Set CTA button. `onClick: ((AdropNativeAd?, UIView) -> Void)? = nil` | Optional |
| `setAdvertiserView(_:onClick:)` | Set advertiser view. `onClick: ((AdropNativeAd?, UIView) -> Void)? = nil` | Optional |
| `setProfileLogoView(_:onClick:)` | Set advertiser logo image view. `onClick: ((AdropNativeAd?, UIView) -> Void)? = nil` | Optional |
| `setProfileNameView(_:onClick:)` | Set advertiser name label. `onClick: ((AdropNativeAd?, UIView) -> Void)? = nil` | Optional |
| `setNativeAd(_:)` | Set ad data | Required |
The `onClick` closure is called when the user taps the bound view. It receives the current `AdropNativeAd?` instance and the tapped `UIView` as parameters. If not provided, the default click behavior (opening the destination URL) is used.
To display advertiser profile, you must enable Show Advertiser Profile for the ad unit in Ad Control Console.
***
## 4. Handle Delegates
Implement `AdropNativeAdDelegate` to handle ad events.
```swift Swift theme={null}
extension MyViewController: AdropNativeAdDelegate {
// Ad received successfully
func onAdReceived(_ nativeAd: AdropNativeAd) {
print("Native ad received successfully")
// Access ad data (properties are non-optional)
print("Title: \(nativeAd.headline)")
print("Description: \(nativeAd.body)")
print("CTA: \(nativeAd.callToAction)")
// Bind ad to view
configureNativeAdView(with: nativeAd)
}
// Ad receive failed
func onAdFailedToReceive(_ nativeAd: AdropNativeAd, _ errorCode: AdropErrorCode) {
print("Native ad failed to receive: \(errorCode)")
}
// Ad clicked (optional)
func onAdClicked(_ nativeAd: AdropNativeAd) {
print("Native ad clicked")
}
// Ad impression (optional)
func onAdImpression(_ nativeAd: AdropNativeAd) {
print("Native ad impression")
}
}
```
```objc Objective-C theme={null}
#pragma mark - AdropNativeAdDelegate
// Ad received successfully
- (void)onAdReceived:(AdropNativeAd *)nativeAd {
NSLog(@"Native ad received successfully");
// Access ad data
if (nativeAd.headline) {
NSLog(@"Title: %@", nativeAd.headline);
}
if (nativeAd.body) {
NSLog(@"Description: %@", nativeAd.body);
}
if (nativeAd.callToAction) {
NSLog(@"CTA: %@", nativeAd.callToAction);
}
// Bind ad to view
[self configureNativeAdViewWith:nativeAd];
}
// Ad receive failed
- (void)onAdFailedToReceive:(AdropNativeAd *)nativeAd :(AdropErrorCode)errorCode {
NSLog(@"Native ad failed to receive: %ld", (long)errorCode);
}
// Ad clicked (optional)
- (void)onAdClicked:(AdropNativeAd *)nativeAd {
NSLog(@"Native ad clicked");
}
// Ad impression (optional)
- (void)onAdImpression:(AdropNativeAd *)nativeAd {
NSLog(@"Native ad impression");
}
```
### Delegate Methods
| Method | Required | Description |
| - | - | - |
| `onAdReceived(_:)` | Required | Called when ad loads successfully |
| `onAdFailedToReceive(_:_:)` | Required | Called when ad fails to load |
| `onAdClicked(_:)` | Optional | Called when user clicks the ad |
| `onAdImpression(_:)` | Optional | Called when ad is displayed |
| `onAdVideoStart(_:)` | Optional | Called when a video ad starts playing |
| `onAdVideoEnd(_:)` | Optional | Called when a video ad finishes playing |
***
## Closure Callbacks
As an alternative to delegates, you can use closure-based callbacks.
```swift theme={null}
let nativeAd = AdropNativeAd(unitId: "YOUR_UNIT_ID")
nativeAd.onAdReceived = { ad in
print("Native ad received")
}
nativeAd.onAdFailedToReceive = { ad, errorCode in
print("Native ad failed: \(errorCode)")
}
nativeAd.onAdImpression = { ad in
print("Native ad impression")
}
nativeAd.onAdClicked = { ad in
print("Native ad clicked")
}
nativeAd.onAdVideoStart = { ad in
print("Native ad video started")
}
nativeAd.onAdVideoEnd = { ad in
print("Native ad video ended")
}
```
If both a delegate and closures are set, both will be called.
***
## Ad Properties
You can access ad data through the `AdropNativeAd` object.
### Basic Properties
```swift theme={null}
// When ad is received successfully
func onAdReceived(_ nativeAd: AdropNativeAd) {
// Title
print("Title: \(nativeAd.headline)")
// Description
print("Description: \(nativeAd.body)")
// CTA button text
print("CTA: \(nativeAd.callToAction)")
// Main image/video
print("Media URL: \(nativeAd.asset)")
}
```
### Advertiser Profile
```swift theme={null}
// Advertiser information (profile and its properties are non-optional)
let profile = nativeAd.profile
print("Advertiser logo: \(profile.displayLogo)")
print("Advertiser name: \(profile.displayName)")
// Profile link (optional — links to the advertiser's page)
if let link = profile.link {
print("Profile link: \(link)")
}
```
### Custom Fields
You can access additional text items set in Ad Control Console.
```swift theme={null}
// Additional text items (extra is non-optional [String: String])
let extra = nativeAd.extra
if !extra.isEmpty {
// Example: Price information
if let price = extra["price"] {
print("Price: \(price)")
}
// Example: Discount rate
if let discount = extra["discount"] {
print("Discount: \(discount)")
}
// Example: Rating
if let rating = extra["rating"] {
print("Rating: \(rating)")
}
}
```
The `extra` field uses additional text item IDs defined in the ad unit settings in Ad Control Console as keys.
### Property List
| Property | Type | Description |
| - | - | - |
| `unitId` | `String` | Ad unit ID |
| `contextId` | `String` | Context ID for contextual targeting |
| `headline` | `String` | Ad title |
| `body` | `String` | Ad description |
| `callToAction` | `String` | CTA button text |
| `icon` | `String` | Icon image URL |
| `cover` | `String` | Cover image URL |
| `asset` | `String` | Main image/video URL |
| `creative` | `String` | Creative HTML content |
| `creativeSize` | `CGSize` | Creative content size |
| `advertiser` | `String` | Advertiser name |
| `advertiserURL` | `String` | Advertiser URL |
| `profile` | `AdropNativeAdProfile` | Advertiser profile information |
| `extra` | `[String: String]` | Additional custom fields |
| `accountTag` | `[String: Any]` | Account-level tag data |
| `creativeTag` | `[String: Any]` | Creative-level tag data |
| `destinationURL` | `String?` | Ad destination URL |
| `txId` | `String` | Transaction ID |
| `campaignId` | `String` | Campaign ID |
| `creativeId` | `String` | Creative ID |
| `browserTarget` | `BrowserTarget?` | How to open URLs on ad click |
| `isLoaded` | `Bool` | Whether ad is loaded |
| `isBackfilled` | `Bool` | Whether ad is a backfill ad |
### AdropNativeAdProfile
| Property | Type | Description |
| - | - | - |
| `displayLogo` | `String` | Advertiser logo image URL |
| `displayName` | `String` | Advertiser display name |
| `link` | `String?` | Link to the advertiser's page |
***
## Custom Click
You can use Custom Click to handle ad click behavior yourself instead of using the default behavior (opening the destination URL).
### Setup
```swift Swift theme={null}
let nativeAd = AdropNativeAd(unitId: "YOUR_UNIT_ID")
nativeAd.useCustomClick = true
```
```objc Objective-C theme={null}
AdropNativeAd *nativeAd = [[AdropNativeAd alloc] initWithUnitId:@"YOUR_UNIT_ID"];
nativeAd.useCustomClick = YES;
```
### Entire Click Area
Use `setIsEntireClick(_:)` to make the entire `AdropNativeAdView` clickable.
```swift theme={null}
nativeAdView.setIsEntireClick(true)
```
When `useCustomClick` is `true`, `setIsEntireClick(true)` is automatically called in `setNativeAd(_:)`.
### Manual Click
Use `performClick()` to manually trigger a click event.
```swift theme={null}
nativeAdView.performClick()
```
### Open URL
Use `open(_:useInAppBrowser:)` to open a URL programmatically.
```swift theme={null}
// Open destination URL
nativeAd.open(nativeAd.destinationURL)
// Open in in-app browser
nativeAd.open(nativeAd.destinationURL, useInAppBrowser: true)
```
***
## Video Tracking and VTR
Adrop measures video metrics for native creatives — including impression-to-completion (VTR), `onAdVideoStart`, and `onAdVideoEnd` — only when the video is rendered through the SDK's media surface (a `UIView` bound via `AdropNativeAdView.setMediaView(_:)`).
The `AdropNativeAd.asset` property returns a URL pointing to the underlying image or video file. It is exposed as supplementary metadata (for example, for thumbnails or your own analytics); it is **not** intended to drive playback.
Do not feed the `asset` URL into a custom video player (`AVPlayer`, `AVPlayerViewController`, or any third-party player) for a video native ad. When the SDK's media surface is bypassed:
* **VTR (Video Through Rate) is not collected** for that placement.
* `onAdVideoStart` / `onAdVideoEnd` callbacks **never fire**.
* Aggregate video performance for the unit will under-report or report zero.
Click (`onAdClicked`) and impression (`onAdImpression`) tracking still work because they are wired to the container view, but the video-specific signals are lost.
### Recommended pattern
Bind the SDK media surface for every video native:
```swift Swift theme={null}
nativeAdView.setMediaView(mediaView) // Required for video tracking
nativeAdView.setNativeAd(ad)
```
```objc Objective-C theme={null}
[self.nativeAdView setMediaView:self.mediaView]; // Required for video tracking
[self.nativeAdView setNativeAd:ad];
```
If a placement absolutely requires a custom player and you accept that VTR will not be measured, you can still surface click and impression attribution by keeping the ad bound to `AdropNativeAdView`. In that case, treat the placement as **non-VTR inventory** in your internal reporting and avoid mixing it with SDK-measured video performance.
***
## Best Practices
### Error Handling
Display fallback UI when ad fails to load to maintain user experience.
```swift theme={null}
func onAdFailedToReceive(_ nativeAd: AdropNativeAd, _ errorCode: AdropErrorCode) {
print("Native ad failed to receive: \(errorCode)")
// Hide ad area
nativeAdContainerView.isHidden = true
// Or display fallback content
showFallbackContent()
}
```
***
## Test Unit IDs
Use the following test unit IDs during development and testing.
| Ad Type | Test Unit ID |
| - | - |
| Native (Image) | `PUBLIC_TEST_UNIT_ID_NATIVE` |
| Native Video (16:9) | `PUBLIC_TEST_UNIT_ID_NATIVE_VIDEO_16_9` |
| Native Video (9:16) | `PUBLIC_TEST_UNIT_ID_NATIVE_VIDEO_9_16` |
```swift theme={null}
// Load test ad
nativeAd = AdropNativeAd(unitId: AdropUnitId.PUBLIC_TEST_UNIT_ID_NATIVE)
```
Make sure to use actual unit IDs created in Ad Control Console for production deployment.
***
## Batch Loading (loads)
Use `AdropNativeAd.loads(...)` to request multiple native ads in a single batched call. This is useful for prefetching a pool of ads to insert into feeds, carousels, or paginated lists.
### Signature
```swift Swift theme={null}
AdropNativeAd.loads(
unitId: String,
contextId: String = "",
delegate: AdropNativeAdDelegate
)
```
```objc Objective-C theme={null}
[AdropNativeAd loadsWithUnitId:@"YOUR_UNIT_ID"
contextId:@""
delegate:delegate];
```
### Usage
```swift Swift theme={null}
class ViewController: UIViewController {
private var ads: [AdropNativeAd] = []
override func viewDidLoad() {
super.viewDidLoad()
AdropNativeAd.loads(unitId: "YOUR_UNIT_ID", delegate: self)
}
}
extension ViewController: AdropNativeAdDelegate {
// Required single-load callbacks — unused on the batch path.
func onAdReceived(_ ad: AdropNativeAd) { /* batch path */ }
func onAdFailedToReceive(_ ad: AdropNativeAd, _ errorCode: AdropErrorCode) {}
// Batch success — each ad is fully rendered and ready to bind.
func onAdsReceived(_ ads: [AdropNativeAd]) {
self.ads = ads
ads.forEach { ad in
bind(ad) // Bind to an AdropNativeAdView.
}
}
// Batch failure
func onAdsFailedToReceive(_ errorCode: AdropErrorCode) {
print("Batch load failed: \(errorCode)")
}
// Click / impression / video callbacks still fire per instance.
func onAdClicked(_ ad: AdropNativeAd) {
print("Native clicked")
}
func onAdImpression(_ ad: AdropNativeAd) {
print("Native impression")
}
}
```
```objc Objective-C theme={null}
@interface ViewController ()
@property (nonatomic, strong) NSArray *ads;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[AdropNativeAd loadsWithUnitId:@"YOUR_UNIT_ID" contextId:@"" delegate:self];
}
#pragma mark - AdropNativeAdDelegate
- (void)onAdReceived:(AdropNativeAd *)ad { /* batch path */ }
- (void)onAdFailedToReceive:(AdropNativeAd *)ad :(AdropErrorCode)errorCode {}
- (void)onAdsReceived:(NSArray *)ads {
self.ads = ads;
for (AdropNativeAd *ad in ads) {
[self bindNativeAd:ad];
}
}
- (void)onAdsFailedToReceive:(AdropErrorCode)errorCode {
NSLog(@"Batch load failed: %ld", (long)errorCode);
}
- (void)onAdClicked:(AdropNativeAd *)ad {
NSLog(@"Native clicked");
}
- (void)onAdImpression:(AdropNativeAd *)ad {
NSLog(@"Native impression");
}
@end
```
### Delegate Methods
| Method | When it fires |
| - | - |
| `onAdsReceived(_:)` | Batch delivered. **Every ad is fully rendered** — creative is ready to bind. |
| `onAdsFailedToReceive(_:)` | Request rejected, network failure, or no fillable ads were returned (`ERROR_CODE_AD_NO_FILL`). |
| `onAdClicked` / `onAdImpression` / `onAdVideoStart` / `onAdVideoEnd` | Fire on each returned ad just like a single `load()`. |
The singular `onAdReceived(_:)` / `onAdFailedToReceive(_:_:)` callbacks are **not** invoked on the `loads` path. Use `onAdsReceived(_:)` / `onAdsFailedToReceive(_:)` as the sole batch signals.
### Constants
Upper bound on ads returned from a single `loads(...)` call.
### Constraints
* **Maximum 5 ads per call.** If the server returns more, only the first 5 are delivered.
* **Backfill is not applied.** If no direct ads fill, `onAdsFailedToReceive` is called with `ERROR_CODE_AD_NO_FILL` regardless of backfill configuration.
* Unlike `AdropBanner.loads(...)`, the native batch waits for every returned ad to finish rendering before firing `onAdsReceived(_:)`, so each instance is fully rendered on delivery.
* Hold strong references to the returned ads. If they deallocate before being bound to an `AdropNativeAdView`, they're silently dropped.
***
## Backfill Ads
When backfill ads are enabled, backfill ads are automatically loaded when direct ads are unavailable. Use the `isBackfilled` property to check if the ad is a backfill ad.
```swift Swift theme={null}
class ViewController: UIViewController, AdropNativeAdDelegate {
private var nativeAd: AdropNativeAd?
func onAdReceived(_ nativeAd: AdropNativeAd) {
if nativeAd.isBackfilled {
print("Backfill ad loaded")
} else {
print("Direct ad loaded")
}
}
func onAdFailedToReceive(_ nativeAd: AdropNativeAd, _ errorCode: AdropErrorCode) {
switch errorCode {
case .ERROR_CODE_AD_NO_FILL:
print("No direct ad, requesting backfill...")
case .ERROR_CODE_AD_BACKFILL_NO_FILL:
print("No backfill ad available")
// Hide ad area
default:
print("Ad load failed: \(errorCode)")
}
}
}
```
```objc Objective-C theme={null}
@interface ViewController ()
@property (nonatomic, strong) AdropNativeAd *nativeAd;
@end
@implementation ViewController
- (void)onAdReceived:(AdropNativeAd *)nativeAd {
if (nativeAd.isBackfilled) {
NSLog(@"Backfill ad loaded");
} else {
NSLog(@"Direct ad loaded");
}
}
- (void)onAdFailedToReceive:(AdropNativeAd *)nativeAd :(AdropErrorCode)errorCode {
if (errorCode == AdropErrorCodeAdNoFill) {
NSLog(@"No direct ad, requesting backfill...");
} else if (errorCode == AdropErrorCodeBackfillNoFill) {
NSLog(@"No backfill ad available");
// Hide ad area
} else {
NSLog(@"Ad load failed: %ld", (long)errorCode);
}
}
@end
```
### AdChoices Position
For backfill native ads, you can choose which corner displays the AdChoices badge. Set `preferredAdChoicesPosition` on the `AdropNativeAd` instance before calling `load()`.
```swift Swift theme={null}
import AdropAds
let nativeAd = AdropNativeAd(unitId: "YOUR_UNIT_ID")
nativeAd.preferredAdChoicesPosition = .bottomRight
nativeAd.load()
```
```objc Objective-C theme={null}
@import AdropAds;
AdropNativeAd *nativeAd = [[AdropNativeAd alloc] initWithUnitId:@"YOUR_UNIT_ID"];
nativeAd.preferredAdChoicesPosition = AdropAdChoicesPositionBottomRight;
[nativeAd load];
```
| Value | Description |
| - | - |
| `.topLeft` | Top-left corner |
| `.topRight` | Top-right corner (default) |
| `.bottomLeft` | Bottom-left corner |
| `.bottomRight` | Bottom-right corner |
This setting is a preferred hint for the backfill network and applies only to backfill native ads — direct ads are not affected. The value must be set before `load()` is called. The backfill network may override the position depending on its policy.
To use backfill ads, add the `AdropAds-Backfill` dependency. See [Getting Started](/sdk/ios/overview).
***
## Related Documentation
SDK installation and initialization
Implementing banner ads
Implementing interstitial ads
Implementing rewarded ads
# Getting Started
Source: https://docs.adrop.io/sdk/ios/overview
Guide to integrating Adrop SDK into your iOS project.
## Overview
Use the Adrop iOS SDK to display various ad formats in your app.
### Supported Ad Formats
| Format | Description |
| - | - |
| **Banner** | Rectangular ads displayed in a portion of the screen |
| **Native** | Ads that can be customized to match your app UI |
| **Interstitial** | Full-screen ads that cover the entire screen |
| **Rewarded** | Video ads that provide rewards upon completion |
| **Popup** | Ads displayed as popups at specific moments |
| **Splash** | Ads displayed with your logo when the app starts |
### Requirements
* iOS 13.0 or higher
* Xcode 14.1 or higher
* Swift 5.0 or higher
***
## Prerequisites
### 1. Add adrop\_service.json File
Download the `adrop_service.json` file from [Ad Control Console](https://console.adrop.io) > **Admin** > **App**.
Add the downloaded file to your Xcode project root.
When adding the file, ensure that **all targets are selected**.
The SDK will not function properly without the `adrop_service.json` file.
### 2. Check Unit ID
Check the unit ID for your ad placement in the **Ad Unit** tab of the console.
***
## Installation
### Swift Package Manager (Recommended)
1. In Xcode, select **File > Add Package Dependencies...**
2. Enter the package URL:
```
https://github.com/OpenRhapsody/adrop-ads-pod
```
3. Click **Add Package**
To use backfill ads, add an additional package:
```
https://github.com/OpenRhapsody/adrop-ads-backfill-pod
```
### CocoaPods
1. Create Podfile if it doesn't exist:
```bash theme={null}
pod init
```
2. Add to Podfile:
```ruby title="Podfile" theme={null}
pod 'adrop-ads'
pod 'adrop-ads-backfill'
```
3. Install:
```bash theme={null}
pod install --repo-update
```
4. Open the project with `.xcworkspace` file
### Info.plist Configuration
Add the following settings to `Info.plist` for backfill ads:
* **GADApplicationIdentifier**: App ID for backfill ads
* **SKAdNetworkItems**: Required for ad conversion tracking on iOS 14+
```xml Info.plist theme={null}
GADApplicationIdentifierca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyySKAdNetworkItemsSKAdNetworkIdentifiercstr6suwn9.skadnetworkSKAdNetworkIdentifier4fzdc2evr5.skadnetworkSKAdNetworkIdentifier4pfyvq9l8r.skadnetworkSKAdNetworkIdentifier2fnua5tdw4.skadnetworkSKAdNetworkIdentifierydx93a7ass.skadnetworkSKAdNetworkIdentifier5a6flpkh64.skadnetworkSKAdNetworkIdentifierp78axxw29g.skadnetworkSKAdNetworkIdentifierv72qych5uu.skadnetworkSKAdNetworkIdentifierludvb6z3bs.skadnetworkSKAdNetworkIdentifiercp8zw746q7.skadnetworkSKAdNetworkIdentifierv9wttpbfk9.skadnetworkSKAdNetworkIdentifierlr83yxwka7.skadnetworkSKAdNetworkIdentifier578prtvx9j.skadnetworkSKAdNetworkIdentifier7ug5zh24hu.skadnetworkSKAdNetworkIdentifierwg4vff78zm.skadnetworkSKAdNetworkIdentifiermj797d8u6f.skadnetworkSKAdNetworkIdentifier8s468mfl3y.skadnetworkSKAdNetworkIdentifier4dzt52r2t5.skadnetworkSKAdNetworkIdentifiervhf287vqwu.skadnetworkSKAdNetworkIdentifierc6k4g5qg8m.skadnetworkSKAdNetworkIdentifier5tjdwbrq8w.skadnetworkSKAdNetworkIdentifier294l99pt4k.skadnetworkSKAdNetworkIdentifiermqn7fxpca7.skadnetworkSKAdNetworkIdentifierg6gcrrvk4p.skadnetworkSKAdNetworkIdentifiertl55sbb4fm.skadnetworkSKAdNetworkIdentifiers39g8k73mm.skadnetworkSKAdNetworkIdentifiera2p9lx4jpn.skadnetworkSKAdNetworkIdentifierk6y4y55b64.skadnetworkSKAdNetworkIdentifier488r3q3dtq.skadnetworkSKAdNetworkIdentifier9rd848q2bz.skadnetworkSKAdNetworkIdentifierglqzh8vgby.skadnetworkSKAdNetworkIdentifier5lm9lj6jb7.skadnetworkSKAdNetworkIdentifier97r2b46745.skadnetworkSKAdNetworkIdentifierzmvfpc5aq8.skadnetworkSKAdNetworkIdentifier3sh42y64q3.skadnetworkSKAdNetworkIdentifiermlmmfzh3r3.skadnetworkSKAdNetworkIdentifier3rd42ekr43.skadnetworkSKAdNetworkIdentifier2u9pt9hc89.skadnetworkSKAdNetworkIdentifierm8dbw4sv7c.skadnetworkSKAdNetworkIdentifier238da6jt44.skadnetworkSKAdNetworkIdentifier4468km3ulz.skadnetworkSKAdNetworkIdentifier4w7y6s5ca2.skadnetworkSKAdNetworkIdentifiera8cz6cu7e5.skadnetworkSKAdNetworkIdentifier77y3x8wds4.skadnetworkSKAdNetworkIdentifiern9x2a789qt.skadnetworkSKAdNetworkIdentifierhs6bdukanm.skadnetworkSKAdNetworkIdentifierf7s53z58qe.skadnetworkSKAdNetworkIdentifier9t245vhmpl.skadnetworkSKAdNetworkIdentifiermp6xlyr22a.skadnetworkSKAdNetworkIdentifierprcb7njmu6.skadnetworkSKAdNetworkIdentifierkbd757ywx3.skadnetworkSKAdNetworkIdentifierk674qkevps.skadnetworkSKAdNetworkIdentifiert38b2kh725.skadnetworkSKAdNetworkIdentifierfeyaarzu9v.skadnetworkSKAdNetworkIdentifier5f5u5tfb26.skadnetworkSKAdNetworkIdentifier424m5254lk.skadnetworkSKAdNetworkIdentifiere5fvkxwrpn.skadnetworkSKAdNetworkIdentifier3qy4746246.skadnetworkSKAdNetworkIdentifierklf5c3l5u5.skadnetworkSKAdNetworkIdentifierzq492l623r.skadnetworkSKAdNetworkIdentifier22mmun2rn5.skadnetworkSKAdNetworkIdentifierav6w8kgt66.skadnetworkSKAdNetworkIdentifierv79kvwwj4g.skadnetworkSKAdNetworkIdentifier44jx6755aq.skadnetworkSKAdNetworkIdentifier32z4fx6l9h.skadnetworkSKAdNetworkIdentifierw9q455wk68.skadnetworkSKAdNetworkIdentifierxga6mpmplv.skadnetworkSKAdNetworkIdentifierf38h382jlk.skadnetworkSKAdNetworkIdentifieryclnxrl5pm.skadnetworkSKAdNetworkIdentifierx44k69ngh6.skadnetworkSKAdNetworkIdentifierppxm28t8ap.skadnetworkSKAdNetworkIdentifierf73kdq92p3.skadnetworkSKAdNetworkIdentifier5l3tpt7t6e.skadnetworkSKAdNetworkIdentifierwzmmz9fp6w.skadnetworkSKAdNetworkIdentifierpwa73g5rt2.skadnetworkSKAdNetworkIdentifier9nlqeag3gk.skadnetworkSKAdNetworkIdentifier6yxyv74ff7.skadnetworkSKAdNetworkIdentifieruw77j35x4d.skadnetworkSKAdNetworkIdentifier54nzkqm89y.skadnetworkSKAdNetworkIdentifier44n7hlldy6.skadnetworkSKAdNetworkIdentifier6964rsfnh4.skadnetworkSKAdNetworkIdentifier6g9af3uyq4.skadnetworkSKAdNetworkIdentifier6v7lgmsu45.skadnetworkSKAdNetworkIdentifier7fmhfwg9en.skadnetworkSKAdNetworkIdentifiera7xqa6mtl2.skadnetworkSKAdNetworkIdentifier84993kbrcf.skadnetworkSKAdNetworkIdentifierbxvub5ada5.skadnetworkSKAdNetworkIdentifier79pbpufp6p.skadnetworkSKAdNetworkIdentifier9vvzujtq5s.skadnetworkSKAdNetworkIdentifier7rz58n8ntl.skadnetworkSKAdNetworkIdentifier3qcr597p9d.skadnetworkSKAdNetworkIdentifier737z793b9f.skadnetworkSKAdNetworkIdentifier89z7zv988g.skadnetworkSKAdNetworkIdentifierc3frkrj4fj.skadnetworkSKAdNetworkIdentifier275upjj5gd.skadnetworkSKAdNetworkIdentifier3l6bd9hu43.skadnetworkSKAdNetworkIdentifier52fl2v3hgk.skadnetworkSKAdNetworkIdentifier6p4ks3rnbw.skadnetworkSKAdNetworkIdentifierapzhy3va96.skadnetworkSKAdNetworkIdentifier9b89h5y424.skadnetworkSKAdNetworkIdentifier523jb4fst2.skadnetworkSKAdNetworkIdentifierb9bk5wbcq9.skadnetworkSKAdNetworkIdentifiercg4yq2srnc.skadnetworkSKAdNetworkIdentifiercj5566h2ga.skadnetworkSKAdNetworkIdentifier24zw6aqk47.skadnetworkSKAdNetworkIdentifier74b6s63p6l.skadnetworkSKAdNetworkIdentifier8m87ys6875.skadnetworkSKAdNetworkIdentifier24t9a8vw3c.skadnetworkSKAdNetworkIdentifier6xzpu9s2p8.skadnetworkSKAdNetworkIdentifiercs644xg564.skadnetworkSKAdNetworkIdentifierdbu4b84rxf.skadnetworkSKAdNetworkIdentifierejvt5qm6ak.skadnetworkSKAdNetworkIdentifiermtkv5xtk9e.skadnetworkSKAdNetworkIdentifierkbmxgpxpgc.skadnetworkSKAdNetworkIdentifierhb56zgv37p.skadnetworkSKAdNetworkIdentifierpwdxu55a5a.skadnetworkSKAdNetworkIdentifierr45fhb6rf7.skadnetworkSKAdNetworkIdentifiert6d3zquu66.skadnetworkSKAdNetworkIdentifieru679fj5vs4.skadnetworkSKAdNetworkIdentifierdzg6xy7pwj.skadnetworkSKAdNetworkIdentifierg28c52eehv.skadnetworkSKAdNetworkIdentifiermls7yz5dvl.skadnetworkSKAdNetworkIdentifiervcra2ehyfk.skadnetworkSKAdNetworkIdentifierrx5hdcabgc.skadnetworkSKAdNetworkIdentifierqwpu75vrh2.skadnetworkSKAdNetworkIdentifierrvh3l7un93.skadnetworkSKAdNetworkIdentifierx5l83yy675.skadnetworkSKAdNetworkIdentifierm5mvw97r93.skadnetworkSKAdNetworkIdentifierxy9t38ct57.skadnetworkSKAdNetworkIdentifierg2y4y55b64.skadnetworkSKAdNetworkIdentifiergta9lk7p23.skadnetworkSKAdNetworkIdentifiern6fk4nfna4.skadnetworkSKAdNetworkIdentifierqu637u8glc.skadnetworkSKAdNetworkIdentifiery5ghdn5j9k.skadnetworkSKAdNetworkIdentifiery45688jllp.skadnetworkSKAdNetworkIdentifierggvn48r87g.skadnetworkSKAdNetworkIdentifierm297p6643m.skadnetworkSKAdNetworkIdentifierqqp299437r.skadnetworkSKAdNetworkIdentifierhdw39hrw9y.skadnetworkSKAdNetworkIdentifierkrvm3zuq6h.skadnetworkSKAdNetworkIdentifiercwn433xbcr.skadnetworkSKAdNetworkIdentifierns5j362hk7.skadnetworkSKAdNetworkIdentifiersu67r6k2v3.skadnetworkSKAdNetworkIdentifierz959bm4gru.skadnetworkSKAdNetworkIdentifierx8uqf25wch.skadnetworkSKAdNetworkIdentifiern38lu8286q.skadnetworkSKAdNetworkIdentifierfz2k2k5tej.skadnetworkSKAdNetworkIdentifierecpz2srf59.skadnetworkSKAdNetworkIdentifierbvpn9ufa9b.skadnetworkSKAdNetworkIdentifier6rd35atwn8.skadnetworkSKAdNetworkIdentifierln5gz23vtd.skadnetworkSKAdNetworkIdentifiertmhh9296z4.skadnetworkSKAdNetworkIdentifiersczv5946wb.skadnetworkSKAdNetworkIdentifierdkc879ngq3.skadnetworkSKAdNetworkIdentifier87u5trcl3r.skadnetworkSKAdNetworkIdentifierfq6vru337s.skadnetworkSKAdNetworkIdentifierf2zub97jtl.skadnetworkSKAdNetworkIdentifierthzdn4h5nc.adattributionkitSKAdNetworkIdentifierraa6f494kr.adattributionkitSKAdNetworkIdentifier6lz2ygh3q6.adattributionkitSKAdNetworkIdentifierm2jqnlggk3.adattributionkitSKAdNetworkIdentifierpg7ctvrt6f.adattributionkitSKAdNetworkIdentifier77y3x8wds4.adattributionkitSKAdNetworkIdentifier55644vm79v.skadnetworkSKAdNetworkIdentifiernzq8sh4pbs.skadnetworkSKAdNetworkIdentifier55y65gfgn7.skadnetworkSKAdNetworkIdentifier252b5q8x7y.skadnetworkSKAdNetworkIdentifier47vhws6wlr.skadnetworkSKAdNetworkIdentifier4mn522wn87.skadnetworkSKAdNetworkIdentifier577p5t736z.skadnetworkSKAdNetworkIdentifier7953jerfzd.skadnetworkSKAdNetworkIdentifier7bxrt786m8.skadnetworkSKAdNetworkIdentifier7fbxrn65az.skadnetworkSKAdNetworkIdentifier8c4e2ghe7u.skadnetworkSKAdNetworkIdentifier8r8llnkz5a.skadnetworkSKAdNetworkIdentifier9yg77x724h.skadnetworkSKAdNetworkIdentifierce8ybjwass.skadnetworkSKAdNetworkIdentifierdt3cjx1a9i.skadnetworkSKAdNetworkIdentifiereh6m2bh4zr.skadnetworkSKAdNetworkIdentifiergta8lk7p23.skadnetworkSKAdNetworkIdentifiergvmwg8q7h5.skadnetworkSKAdNetworkIdentifierhjevpa356n.skadnetworkSKAdNetworkIdentifierjk2fsx2rgz.skadnetworkSKAdNetworkIdentifiern66cz3y3bx.skadnetworkSKAdNetworkIdentifierpu4na253f3.skadnetworkSKAdNetworkIdentifierr8lj5b58b5.skadnetworkSKAdNetworkIdentifiers69wq72ugq.skadnetworkSKAdNetworkIdentifierv4nxqhlyqp.skadnetworkSKAdNetworkIdentifiervutu7akeur.skadnetworkSKAdNetworkIdentifierz24wtl6j62.skadnetworkSKAdNetworkIdentifierz4gj7hsk7h.skadnetworkSKAdNetworkIdentifierx8jxxk4ff5.skadnetworkSKAdNetworkIdentifierx8yj322td6.skadnetworkSKAdNetworkIdentifier23zd986j2c.skadnetworkSKAdNetworkIdentifiercad8qz2s3j.skadnetwork
```
If your Info.plist already contains a `SKAdNetworkItems` key, do not add a new one — merge only the array (``) entries. Duplicate keys cause iOS to use only the first array.
Please contact Adrop for the full SKAdNetworkItems list and App ID.
***
## Initialization
Initialize the SDK in `application(_:didFinishLaunchingWithOptions:)` of your `AppDelegate`.
```swift Swift (UIKit) theme={null}
import AdropAds
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// Initialize SDK
Adrop.initialize(production: false, useInAppBrowser: false, targetCountries: [])
Adrop.setTheme(.auto)
return true
}
}
```
```swift Swift (SwiftUI) theme={null}
import SwiftUI
import AdropAds
class AppDelegate: NSObject, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
// Initialize SDK
Adrop.initialize(production: false, useInAppBrowser: false, targetCountries: [])
Adrop.setTheme(.auto)
return true
}
}
@main
struct MyApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
```
```objc Objective-C theme={null}
@import AdropAds;
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Initialize SDK
[Adrop initializeWithProduction:NO useInAppBrowser:NO targetCountries:@[]];
[Adrop setTheme:AdropThemeAuto];
return YES;
}
@end
```
### Initialization Parameters
| Parameter | Type | Default | Description |
| - | - | - | - |
| `production` | `Bool` | `false` | Production mode. Set to `true` when deploying |
| `targetCountries` | `[String]?` | `nil` | List of target country codes (ISO 3166 alpha-2). `nil` for all countries |
| `useInAppBrowser` | `Bool` | `false` | Whether to use in-app browser |
Make sure to set `production: true` before deployment. If deployed with `false`, ads will not be displayed.
***
## User Configuration
### Set UID
Set a user identifier for targeted advertising.
```swift Swift theme={null}
Adrop.setUID("user_123")
```
```objc Objective-C theme={null}
[Adrop setUID:@"user_123"];
```
Set the UID before entering the ad placement for targeted ads to work properly.
### Set Theme
Set the theme for ads that support dark mode. Must be set after calling `initialize()`.
```swift Swift theme={null}
// Light mode
Adrop.setTheme(.light)
// Dark mode
Adrop.setTheme(.dark)
// Follow system settings (recommended)
Adrop.setTheme(.auto)
```
```objc Objective-C theme={null}
// Light mode
[Adrop setTheme:AdropThemeLight];
// Dark mode
[Adrop setTheme:AdropThemeDark];
// Follow system settings (recommended)
[Adrop setTheme:AdropThemeAuto];
```
`auto` mode automatically detects system dark mode settings. When the theme changes, the splash ad cache is automatically reset.
***
## WebView Support
Register a `WKWebView` to display web ads within a WebView.
```swift Swift theme={null}
import WebKit
import AdropAds
Adrop.registerWebView(webView)
```
```objc Objective-C theme={null}
@import AdropAds;
[Adrop registerWebView:webView];
```
You can check the current SDK version using `Adrop.sdkVersion`.
***
## Deep Link Handling
Handle deep links when the app is launched via external links.
```swift Swift (SceneDelegate) theme={null}
func scene(_ scene: UIScene, openURLContexts URLContexts: Set) {
guard let url = URLContexts.first?.url else { return }
Adrop.handleDeepLink(url: url)
}
```
```swift Swift (AppDelegate) theme={null}
func application(
_ app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey: Any] = [:]
) -> Bool {
Adrop.handleDeepLink(url: url)
return true
}
```
```objc Objective-C theme={null}
- (BOOL)application:(UIApplication *)app
openURL:(NSURL *)url
options:(NSDictionary *)options {
[Adrop handleDeepLinkWithUrl:url];
return YES;
}
```
***
## Test Unit IDs
Use test unit IDs during development. Replace with actual unit IDs before production deployment.
### Banner Ads
| Format | Test Unit ID |
| - | - |
| Banner (320x50) | `PUBLIC_TEST_UNIT_ID_320_50` |
| Banner (320x100) | `PUBLIC_TEST_UNIT_ID_320_100` |
| Carousel Banner | `PUBLIC_TEST_UNIT_ID_CAROUSEL` |
| Banner Video (16:9) | `PUBLIC_TEST_UNIT_ID_BANNER_VIDEO_16_9` |
| Banner Video (9:16) | `PUBLIC_TEST_UNIT_ID_BANNER_VIDEO_9_16` |
### Native Ads
| Format | Test Unit ID |
| - | - |
| Native (Image) | `PUBLIC_TEST_UNIT_ID_NATIVE` |
| Native Video (16:9) | `PUBLIC_TEST_UNIT_ID_NATIVE_VIDEO_16_9` |
| Native Video (9:16) | `PUBLIC_TEST_UNIT_ID_NATIVE_VIDEO_9_16` |
### Interstitial/Rewarded Ads
| Format | Test Unit ID |
| - | - |
| Interstitial | `PUBLIC_TEST_UNIT_ID_INTERSTITIAL` |
| Rewarded | `PUBLIC_TEST_UNIT_ID_REWARDED` |
### Popup Ads
| Format | Test Unit ID |
| - | - |
| Popup (Bottom) | `PUBLIC_TEST_UNIT_ID_POPUP_BOTTOM` |
| Popup (Center) | `PUBLIC_TEST_UNIT_ID_POPUP_CENTER` |
| Popup Video Bottom (16:9) | `PUBLIC_TEST_UNIT_ID_POPUP_BOTTOM_VIDEO_16_9` |
| Popup Video Bottom (9:16) | `PUBLIC_TEST_UNIT_ID_POPUP_BOTTOM_VIDEO_9_16` |
| Popup Video Center (16:9) | `PUBLIC_TEST_UNIT_ID_POPUP_CENTER_VIDEO_16_9` |
| Popup Video Center (9:16) | `PUBLIC_TEST_UNIT_ID_POPUP_CENTER_VIDEO_9_16` |
### Splash Ads
| Format | Test Unit ID |
| - | - |
| Splash | `PUBLIC_TEST_UNIT_ID_SPLASH` |
***
## Error Codes
Main error codes returned when ad loading fails.
| Error Code | Description |
| - | - |
| `ERROR_CODE_AD_NO_FILL` | No available ads to display |
| `ERROR_CODE_NETWORK` | Network connection failed |
| `ERROR_CODE_INVALID_UNIT` | Invalid unit ID |
| `ERROR_CODE_INITIALIZE` | SDK initialization required |
See [Reference](/sdk/ios/reference) for detailed error codes.
***
## Table of Contents
Implementing banner ads
Implementing native ads
Implementing interstitial ads
Implementing rewarded ads
Implementing popup ads
Implementing splash ads
Display web ads in WebView
GDPR/CCPA consent management
User attributes and contextual targeting
Classes, delegates, error codes
Example repository
# Popup Ads
Source: https://docs.adrop.io/sdk/ios/popup
Guide to implementing popup ads in iOS apps.
## Overview
Popup ads are ad formats that appear on screen at specific moments. They can be displayed at desired timings such as app launch, content load completion, or specific event occurrences. Users can close them by tapping the close button or selecting "Don't show for today."
### Features
* Popup format displayed at center or bottom of screen
* Supports both image and video ads
* Provides close, dim (background) click, and "Don't show for today" options
* Customizable background color, text color, etc.
* Non-intrusive yet effective ad experience
Use test unit IDs in development environment. See the [Test Unit IDs](#test-unit-ids) section.
***
## Implementation Steps
Implement popup ads in 4 steps:
1. **Initialize** - Create AdropPopupAd instance
2. **Set Delegates** - Set delegates to receive ad and close events
3. **Load Ad** - Request and receive ad
4. **Show Ad** - Display ad on screen
***
## UIKit Implementation
### Basic Implementation
```swift Swift theme={null}
import AdropAds
class ViewController: UIViewController {
private var popupAd: AdropPopupAd?
override func viewDidLoad() {
super.viewDidLoad()
loadPopupAd()
}
// 1. Initialize and load ad
private func loadPopupAd() {
popupAd = AdropPopupAd(unitId: "YOUR_POPUP_UNIT_ID")
popupAd?.delegate = self
popupAd?.closeDelegate = self
popupAd?.load()
}
// 2. Show ad
private func showPopupAd() {
guard let popupAd = popupAd,
popupAd.isLoaded else { return }
popupAd.show(fromRootViewController: self)
}
}
```
```objc Objective-C theme={null}
@import AdropAds;
@interface ViewController ()
@property (nonatomic, strong) AdropPopupAd *popupAd;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self loadPopupAd];
}
// 1. Initialize and load ad
- (void)loadPopupAd {
self.popupAd = [[AdropPopupAd alloc] initWithUnitId:@"YOUR_POPUP_UNIT_ID"];
self.popupAd.delegate = self;
self.popupAd.closeDelegate = self;
[self.popupAd load];
}
// 2. Show ad
- (void)showPopupAd {
if (self.popupAd && self.popupAd.isLoaded) {
[self.popupAd showFromRootViewController:self];
}
}
@end
```
### Delegate Implementation
```swift Swift theme={null}
// Ad delegate implementation
extension ViewController: AdropPopupAdDelegate {
// Ad received successfully (required)
func onAdReceived(_ ad: AdropPopupAd) {
print("Popup ad received")
// Show ad when ready
showPopupAd()
}
// Ad receive failed (required)
func onAdFailedToReceive(_ ad: AdropPopupAd, _ errorCode: AdropErrorCode) {
print("Popup ad failed to receive: \(errorCode)")
}
// Ad impression (optional)
func onAdImpression(_ ad: AdropPopupAd) {
print("Popup ad impression")
}
// Ad clicked (optional)
func onAdClicked(_ ad: AdropPopupAd) {
print("Popup ad clicked")
}
// Before showing popup ad (optional)
func onAdWillPresentFullScreen(_ ad: AdropPopupAd) {
print("About to show popup ad")
}
// After showing popup ad (optional)
func onAdDidPresentFullScreen(_ ad: AdropPopupAd) {
print("Popup ad shown")
}
// Before dismissing popup ad (optional)
func onAdWillDismissFullScreen(_ ad: AdropPopupAd) {
print("About to dismiss popup ad")
}
// After dismissing popup ad (optional)
func onAdDidDismissFullScreen(_ ad: AdropPopupAd) {
print("Popup ad dismissed")
// Preload next ad
loadPopupAd()
}
// Popup ad show failed (optional)
func onAdFailedToShowFullScreen(_ ad: AdropPopupAd, _ errorCode: AdropErrorCode) {
print("Popup ad failed to show: \(errorCode)")
}
}
// Close delegate implementation
extension ViewController: AdropPopupAdCloseDelegate {
// Close button clicked (optional)
func onClosed(_ ad: AdropPopupAd) {
print("Popup ad close button clicked")
}
// Dim (background) clicked (optional)
func onDimClicked(_ ad: AdropPopupAd) {
print("Popup ad dim area clicked")
}
// "Don't show for today" clicked (optional)
func onTodayOffClicked(_ ad: AdropPopupAd) {
print("Don't show for today selected")
// The SDK automatically handles hiding the ad for today.
// Use this callback for analytics or custom UI updates.
}
}
```
```objc Objective-C theme={null}
#pragma mark - AdropPopupAdDelegate
// Ad received successfully (required)
- (void)onAdReceived:(AdropPopupAd *)ad {
NSLog(@"Popup ad received");
// Show ad when ready
[self showPopupAd];
}
// Ad receive failed (required)
- (void)onAdFailedToReceive:(AdropPopupAd *)ad :(AdropErrorCode)errorCode {
NSLog(@"Popup ad failed to receive: %ld", (long)errorCode);
}
// Ad impression (optional)
- (void)onAdImpression:(AdropPopupAd *)ad {
NSLog(@"Popup ad impression");
}
// Ad clicked (optional)
- (void)onAdClicked:(AdropPopupAd *)ad {
NSLog(@"Popup ad clicked");
}
// After dismissing popup ad (optional)
- (void)onAdDidDismissFullScreen:(AdropPopupAd *)ad {
NSLog(@"Popup ad dismissed");
// Preload next ad
[self loadPopupAd];
}
#pragma mark - AdropPopupAdCloseDelegate
// Close button clicked (optional)
- (void)onClosed:(AdropPopupAd *)ad {
NSLog(@"Popup ad close button clicked");
}
// Dim (background) clicked (optional)
- (void)onDimClicked:(AdropPopupAd *)ad {
NSLog(@"Popup ad dim area clicked");
}
// "Don't show for today" clicked (optional)
- (void)onTodayOffClicked:(AdropPopupAd *)ad {
NSLog(@"Don't show for today selected");
// The SDK automatically handles hiding the ad for today.
// Use this callback for analytics or custom UI updates.
}
```
***
## SwiftUI Implementation
In SwiftUI, use `UIViewControllerRepresentable` or get the rootViewController from UIWindow to display.
### ViewModel Pattern
```swift theme={null}
import SwiftUI
import AdropAds
// ViewModel
class PopupAdViewModel: ObservableObject {
@Published var isAdReady = false
@Published var isAdShowing = false
private var popupAd: AdropPopupAd?
init() {
loadAd()
}
func loadAd() {
popupAd = AdropPopupAd(unitId: "YOUR_POPUP_UNIT_ID")
popupAd?.delegate = self
popupAd?.closeDelegate = self
popupAd?.load()
}
func showAd() {
guard let popupAd = popupAd,
let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let rootViewController = windowScene.windows.first?.rootViewController else {
return
}
popupAd.show(fromRootViewController: rootViewController)
}
}
// Ad delegate
extension PopupAdViewModel: AdropPopupAdDelegate {
func onAdReceived(_ ad: AdropPopupAd) {
DispatchQueue.main.async {
self.isAdReady = true
}
}
func onAdFailedToReceive(_ ad: AdropPopupAd, _ errorCode: AdropErrorCode) {
print("Ad receive failed: \(errorCode)")
}
func onAdDidPresentFullScreen(_ ad: AdropPopupAd) {
DispatchQueue.main.async {
self.isAdShowing = true
}
}
func onAdDidDismissFullScreen(_ ad: AdropPopupAd) {
DispatchQueue.main.async {
self.isAdShowing = false
self.isAdReady = false
}
// Preload next ad
loadAd()
}
}
// Close delegate
extension PopupAdViewModel: AdropPopupAdCloseDelegate {
func onClosed(_ ad: AdropPopupAd) {
print("Popup ad closed")
}
func onDimClicked(_ ad: AdropPopupAd) {
print("Popup ad dim clicked")
}
func onTodayOffClicked(_ ad: AdropPopupAd) {
print("Don't show for today selected")
// The SDK automatically handles hiding the ad for today.
// Use this callback for analytics or custom UI updates.
}
}
// View
struct ContentView: View {
@StateObject private var adViewModel = PopupAdViewModel()
var body: some View {
VStack {
Text("Popup Ad Example")
.font(.title)
Button("Show Popup Ad") {
adViewModel.showAd()
}
.disabled(!adViewModel.isAdReady)
.padding()
}
.onAppear {
// Auto show on app start
if adViewModel.isAdReady {
adViewModel.showAd()
}
}
}
}
```
***
## Customization
You can customize the appearance of popup ads.
### Set Background Color and Text Color
```swift theme={null}
private func loadPopupAd() {
popupAd = AdropPopupAd(unitId: "YOUR_POPUP_UNIT_ID")
popupAd?.delegate = self
popupAd?.closeDelegate = self
// Set background color
popupAd?.backgroundColor = UIColor.black.withAlphaComponent(0.8)
// "Don't show for today" text color
popupAd?.hideForTodayTextColor = UIColor.white
// Close button text color
popupAd?.closeTextColor = UIColor.white
// CTA button text color
popupAd?.ctaTextColor = UIColor.white
popupAd?.load()
}
```
### Customization Options
| Property | Type | Description | Default |
| - | - | - | - |
| `backgroundColor` | `UIColor?` | Action bar background color | `nil` |
| `hideForTodayTextColor` | `UIColor?` | "Don't show for today" text color | White |
| `closeTextColor` | `UIColor?` | Close button text color | White |
| `ctaTextColor` | `UIColor?` | CTA (Call to Action) button text color | White |
***
## Delegate Methods
### AdropPopupAdDelegate (Ad Events)
#### Required Methods
Called when ad is received successfully. You can call `show()` at this point to display the ad.
Called when ad fails to load. You can check the cause of failure through the error code.
#### Optional Methods
Called when ad impression is recorded.
Called when user clicks the ad.
Called just before popup ad is displayed.
Called immediately after popup ad is displayed on screen.
Called just before popup ad is dismissed.
Called immediately after popup ad is dismissed. Good time to preload the next ad.
Called when ad fails to show. You can check the cause of failure through the error code.
Called when a video ad starts playing.
Called when a video ad finishes playing.
### AdropPopupAdCloseDelegate (Close Events)
All methods are optional.
Called when user clicks the close button.
Called when user clicks outside the popup (dim area).
Called when user selects "Don't show for today." You can save the date in this callback to prevent showing ads for the rest of the day.
***
## Custom Click Handling
You can take control of ad click behavior by using `useCustomClick`. When enabled, the SDK will not automatically open the destination URL on click, and you can handle it yourself.
### useCustomClick
```swift theme={null}
popupAd = AdropPopupAd(unitId: "YOUR_POPUP_UNIT_ID")
popupAd?.useCustomClick = true
popupAd?.delegate = self
popupAd?.load()
```
When `useCustomClick` is `true`, the SDK will not open the destination URL automatically when the ad is clicked. The `onAdClicked` delegate callback will still fire, and you can use the `open()` method or handle navigation yourself.
### open()
Opens the destination URL of the ad. Can optionally pass a custom URL.
```swift theme={null}
// Open the ad's default destination URL
popupAd?.open()
// Open a custom URL
popupAd?.open("https://example.com")
// Open using in-app browser
popupAd?.open(nil, useInAppBrowser: true)
```
### close()
Programmatically dismiss the popup ad.
```swift theme={null}
popupAd?.close()
```
***
## Properties
| Property | Type | Description |
| - | - | - |
| `unitId` | `String` | The ad unit ID |
| `isLoaded` | `Bool` | Whether the ad has been loaded |
| `useCustomClick` | `Bool` | Whether to handle ad clicks manually |
| `destinationURL` | `String?` | Destination URL of the currently displayed ad |
| `creativeId` | `String?` | Creative ID of the currently displayed ad |
| `txId` | `String` | Transaction ID of the currently displayed ad |
| `txIds` | `[String]` | All transaction IDs of loaded ads |
| `campaignId` | `String` | Campaign ID of the currently displayed ad |
| `campaignIds` | `[String]` | All campaign IDs of loaded ads |
| `creativeIds` | `[String]` | All creative IDs of loaded ads |
| `browserTarget` | `BrowserTarget?` | How to open URLs on ad click |
***
## Closure-based Callbacks
In addition to delegates, you can also use closure-based callbacks. This can be convenient when you don't want to implement a full delegate pattern.
```swift theme={null}
let popupAd = AdropPopupAd(unitId: "YOUR_POPUP_UNIT_ID")
// Ad events
popupAd.onAdReceived = { ad in
print("Ad received")
}
popupAd.onAdFailedToReceive = { ad, errorCode in
print("Ad failed: \(errorCode)")
}
popupAd.onAdClicked = { ad in
print("Ad clicked, destination: \(ad.destinationURL ?? "")")
}
popupAd.onAdImpression = { ad in
print("Ad impression")
}
popupAd.onAdWillPresentFullScreen = { ad in
print("Will present")
}
popupAd.onAdDidPresentFullScreen = { ad in
print("Did present")
}
popupAd.onAdWillDismissFullScreen = { ad in
print("Will dismiss")
}
popupAd.onAdDidDismissFullScreen = { ad in
print("Did dismiss")
}
popupAd.onAdFailedToShowFullScreen = { ad, errorCode in
print("Failed to show: \(errorCode)")
}
popupAd.onAdVideoStart = { ad in
print("Video started")
}
popupAd.onAdVideoEnd = { ad in
print("Video ended")
}
// Close events
popupAd.onClosed = { ad in
print("Closed")
}
popupAd.onDimClicked = { ad in
print("Dim clicked")
}
popupAd.onTodayOffClicked = { ad in
print("Today off clicked")
}
popupAd.load()
```
Delegate callbacks and closure-based callbacks can be used simultaneously. Both will be called.
***
## Implementing "Don't Show for Today"
How to properly handle when users select "Don't show for today."
The SDK automatically manages the "Don't Show for Today" state. The following example shows how to add custom behavior alongside the built-in handling.
### Implementation
The SDK automatically skips loading when the user has selected "Don't show for today." Simply call `load()` — the SDK handles the rest.
```swift theme={null}
class PopupAdManager {
private var popupAd: AdropPopupAd?
// Load and show ad
func loadAndShowAd(from viewController: UIViewController) {
popupAd = AdropPopupAd(unitId: "YOUR_POPUP_UNIT_ID")
popupAd?.delegate = self
popupAd?.closeDelegate = self
popupAd?.load()
}
}
extension PopupAdManager: AdropPopupAdDelegate {
func onAdReceived(_ ad: AdropPopupAd) {
// Auto show after receiving ad
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let rootViewController = windowScene.windows.first?.rootViewController {
ad.show(fromRootViewController: rootViewController)
}
}
func onAdFailedToReceive(_ ad: AdropPopupAd, _ errorCode: AdropErrorCode) {
print("Ad receive failed: \(errorCode)")
}
}
extension PopupAdManager: AdropPopupAdCloseDelegate {
func onTodayOffClicked(_ ad: AdropPopupAd) {
print("Don't show for today selected")
// The SDK automatically handles hiding the ad for today.
// Use this callback for analytics or custom UI updates.
}
}
```
***
## Best Practices
### 1. Appropriate Display Timing
Popup ads are effective when shown at these timings:
```swift theme={null}
// ✅ Good: On app launch
func applicationDidFinishLaunching() {
loadAndShowPopupAd()
}
// ✅ Good: After content load completes
func onContentLoaded() {
if shouldShowPopupAd() {
showPopupAd()
}
}
// ✅ Good: After specific event completes
func onAchievementUnlocked() {
showPopupAd()
}
// ❌ Bad: While user is working
func onUserTyping() {
showPopupAd() // Disrupts user experience
}
```
### 2. Respect "Don't Show for Today"
If user selects "Don't show for today," make sure to honor it.
```swift theme={null}
func shouldShowPopupAd() -> Bool {
// Check "Don't show for today"
guard canShowAdToday() else {
return false
}
// Check other conditions
return true
}
```
### 3. Frequency Capping
Don't show popup ads too frequently.
```swift theme={null}
class PopupAdFrequencyManager {
private let lastShownKey = "popupAd_lastShown"
private let minimumInterval: TimeInterval = 3600 // 1 hour
func canShowAd() -> Bool {
// Check "Don't show for today"
guard canShowAdToday() else {
return false
}
// Check minimum time interval
guard let lastShown = UserDefaults.standard.object(forKey: lastShownKey) as? Date else {
return true
}
return Date().timeIntervalSince(lastShown) >= minimumInterval
}
func recordAdShown() {
UserDefaults.standard.set(Date(), forKey: lastShownKey)
}
}
```
***
## Test Unit IDs
Use the following test unit IDs during development and testing.
| Ad Type | Test Unit ID |
| - | - |
| Popup (Bottom Image) | `PUBLIC_TEST_UNIT_ID_POPUP_BOTTOM` |
| Popup (Center Image) | `PUBLIC_TEST_UNIT_ID_POPUP_CENTER` |
| Popup Video (Bottom 16:9) | `PUBLIC_TEST_UNIT_ID_POPUP_BOTTOM_VIDEO_16_9` |
| Popup Video (Bottom 9:16) | `PUBLIC_TEST_UNIT_ID_POPUP_BOTTOM_VIDEO_9_16` |
| Popup Video (Center 16:9) | `PUBLIC_TEST_UNIT_ID_POPUP_CENTER_VIDEO_16_9` |
| Popup Video (Center 9:16) | `PUBLIC_TEST_UNIT_ID_POPUP_CENTER_VIDEO_9_16` |
Make sure to use actual unit IDs created in Ad Control Console for production deployment.
***
## Related Documentation
SDK installation and initialization
Implementing interstitial ads
Implementing rewarded ads
Implementing banner ads
# Reference
Source: https://docs.adrop.io/sdk/ios/reference
Classes, protocols, enums, and type definitions for the Adrop iOS SDK.
## Classes
### Adrop
The main class of the SDK. Manages SDK initialization and global settings.
#### Static Methods
| Method | Return Type | Description |
| - | - | - |
| `initialize(production:useInAppBrowser:targetCountries:)` | `void` | Initialize SDK |
| `setUID(_:)` | `void` | Set user identifier |
| `setTheme(_:)` | `void` | Set app theme |
| `setMarketingConsent(_:)` | `void` | Set user's marketing consent for push notification ad targeting. Must be called after `initialize()`. |
| `handleDeepLink(url:)` | `Bool` | Handle deep link |
| `openQuest(channel:path:)` | `@discardableResult Bool` | Open Quest screen |
| `registerWebView(_:)` | `void` | Register a WKWebView for backfill ads |
#### Static Properties
| Property | Type | Description |
| - | - | - |
| `sdkVersion` | `String` | SDK version (read-only) |
| `consentManager` | `AdropConsentManager?` | Consent manager (automatically set when AdropAdsBackfill is installed, read/write) |
#### Initialize Method
```swift theme={null}
static func initialize(
production: Bool = false,
useInAppBrowser: Bool = false,
targetCountries: [String]? = nil
)
```
Production mode. Set to `true` when deploying
Whether to use in-app browser. If `true`, ad clicks open within the app
Array of target country codes (e.g., \["KR", "US"]). `nil` for all countries
**Usage Example:**
```swift theme={null}
// AppDelegate.swift
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
Adrop.initialize(production: false)
return true
}
```
#### Set User Identifier
```swift theme={null}
static func setUID(_ uid: String)
```
Unique ID to identify the user (max 100 characters)
**Usage Example:**
```swift theme={null}
Adrop.setUID("user_12345")
```
#### Set Theme
```swift theme={null}
static func setTheme(_ theme: AdropTheme)
```
App theme (light, dark, auto)
**Usage Example:**
```swift theme={null}
Adrop.setTheme(.dark)
```
***
### AdropBanner
Class for displaying banner ads. Inherits from `UIView`.
#### Initialization
```swift theme={null}
init(unitId: String, contextId: String = "")
```
Banner ad unit ID created in the Ad Control Console
Contextual targeting ID (default: empty string)
#### Methods
| Method | Return Type | Description |
| - | - | - |
| `load()` | `void` | Request and load ad |
| `open(_:useInAppBrowser:)` | `void` | Open URL (defaults to `destinationURL`) |
| `play()` | `void` | Start or resume video banner playback |
| `pause()` | `void` | Pause video banner playback |
| `destroy()` | `void` | Release banner resources (idempotent; instance cannot be reused) — see [Lifecycle Management](/sdk/ios/banner#destroy) |
#### Static Methods
| Method | Return Type | Description |
| - | - | - |
| `loads(unitId:contextId:delegate:)` | `void` | Batch load up to 5 banner ads — see [Batch Loading](/sdk/ios/banner#batch-loading-loads) |
#### Static Properties
| Property | Type | Description |
| - | - | - |
| `maxLoadsBatch` | `Int` | Maximum number of ads returned by `loads(...)` (value: `5`) |
#### Properties
| Property | Type | Description |
| - | - | - |
| `unitId` | `String` | Unit ID (read-only) |
| `contextId` | `String` | Contextual targeting ID (read-only, set via init) |
| `creativeSize` | `CGSize` | Ad creative size (read-only, defaults to zero) |
| `destinationURL` | `String?` | Ad destination URL (read-only) |
| `txId` | `String` | Transaction ID (read-only) |
| `campaignId` | `String` | Campaign ID (read-only) |
| `creativeId` | `String` | Creative ID (read-only) |
| `creativeType` | `String` | Loaded creative type (`"display"` or `"video"`). Returns `"display"` until an ad response is received. (read-only) |
| `isBackfilled` | `Bool` | Whether the ad is a backfill ad (read-only) |
| `useCustomClick` | `Bool` | Whether to use custom click handling (read/write) |
| `browserTarget` | `BrowserTarget?` | How to open URLs on ad click (read-only) |
| `delegate` | `AdropBannerDelegate?` | Delegate (read/write) |
| `onAdReceived` | `((AdropBanner) -> Void)?` | Closure callback for ad received |
| `onAdFailedToReceive` | `((AdropBanner, AdropErrorCode) -> Void)?` | Closure callback for ad failed |
| `onAdImpression` | `((AdropBanner) -> Void)?` | Closure callback for ad impression |
| `onAdClicked` | `((AdropBanner) -> Void)?` | Closure callback for ad clicked |
| `onAdVideoStart` | `((AdropBanner) -> Void)?` | Closure callback for video start |
| `onAdVideoEnd` | `((AdropBanner) -> Void)?` | Closure callback for video end |
**Usage Example:**
```swift theme={null}
let banner = AdropBanner(unitId: "YOUR_UNIT_ID", contextId: "sport")
banner.delegate = self
banner.load()
```
***
### AdropNativeAd
Class for managing native ad data.
#### Initialization
```swift theme={null}
init(unitId: String, contextId: String = "")
```
Native ad unit ID created in the Ad Control Console
Contextual targeting ID (default: empty string)
#### Methods
| Method | Return Type | Description |
| - | - | - |
| `load()` | `void` | Request and load ad |
| `open(_:useInAppBrowser:)` | `void` | Open URL (defaults to `destinationURL`) |
#### Static Methods
| Method | Return Type | Description |
| - | - | - |
| `loads(unitId:contextId:delegate:)` | `void` | Batch load up to 5 native ads — see [Batch Loading](/sdk/ios/native#batch-loading-loads) |
#### Static Properties
| Property | Type | Description |
| - | - | - |
| `maxLoadsBatch` | `Int` | Maximum number of ads returned by `loads(...)` (value: `5`) |
#### Properties
| Property | Type | Description |
| - | - | - |
| `unitId` | `String` | Unit ID (read-only) |
| `contextId` | `String` | Contextual targeting ID (read-only, set via init) |
| `txId` | `String` | Transaction ID (read-only) |
| `campaignId` | `String` | Campaign ID (read-only) |
| `creativeId` | `String` | Creative ID (read-only) |
| `creativeType` | `String` | Loaded creative type (`"display"` or `"video"`). Returns `"display"` until an ad response is received. (read-only) |
| `isLoaded` | `Bool` | Whether ad is loaded (read-only) |
| `isBackfilled` | `Bool` | Whether the ad is a backfill ad (read-only) |
| `headline` | `String` | Ad headline (read-only) |
| `body` | `String` | Ad body text (read-only) |
| `callToAction` | `String` | CTA button text (read-only) |
| `icon` | `String` | Ad icon URL (read-only) |
| `cover` | `String` | Ad cover image URL (read-only) |
| `asset` | `String` | Main image URL (read-only) |
| `creative` | `String` | HTML creative content (read-only) |
| `advertiser` | `String` | Advertiser name (read-only) |
| `advertiserURL` | `String` | Advertiser URL (read-only) |
| `profile` | `AdropNativeAdProfile` | Advertiser profile (read-only) |
| `extra` | `[String: String]` | Additional text fields (read-only) |
| `accountTag` | `[String: Any]` | Account tag data (read-only) |
| `creativeTag` | `[String: Any]` | Creative tag data (read-only) |
| `creativeSize` | `CGSize` | Ad creative size (read-only, defaults to zero) |
| `destinationURL` | `String?` | Ad destination URL (read-only) |
| `useCustomClick` | `Bool` | Whether to use custom click handling (read/write) |
| `preferredAdChoicesPosition` | `AdropAdChoicesPosition` | AdChoices badge position for backfill native ads (read/write, default: `.topRight`) |
| `browserTarget` | `BrowserTarget?` | How to open URLs on ad click (read-only) |
| `delegate` | `AdropNativeAdDelegate?` | Delegate (read/write) |
| `onAdReceived` | `((AdropNativeAd) -> Void)?` | Closure callback for ad received |
| `onAdFailedToReceive` | `((AdropNativeAd, AdropErrorCode) -> Void)?` | Closure callback for ad failed |
| `onAdImpression` | `((AdropNativeAd) -> Void)?` | Closure callback for ad impression |
| `onAdClicked` | `((AdropNativeAd) -> Void)?` | Closure callback for ad clicked |
| `onAdVideoStart` | `((AdropNativeAd) -> Void)?` | Closure callback for video start |
| `onAdVideoEnd` | `((AdropNativeAd) -> Void)?` | Closure callback for video end |
**Usage Example:**
```swift theme={null}
let nativeAd = AdropNativeAd(unitId: "YOUR_UNIT_ID")
nativeAd.delegate = self
nativeAd.load()
// After ad loads
titleLabel.text = nativeAd.headline
bodyLabel.text = nativeAd.body
ctaButton.setTitle(nativeAd.callToAction, for: .normal)
```
***
### AdropInterstitialAd
Class for displaying interstitial ads.
#### Initialization
```swift theme={null}
init(unitId: String)
```
Interstitial ad unit ID created in the Ad Control Console
#### Methods
| Method | Return Type | Description |
| - | - | - |
| `load()` | `void` | Preload ad |
| `show(fromRootViewController:)` | `void` | Display ad |
##### show Method
```swift theme={null}
func show(fromRootViewController viewController: UIViewController)
```
Root view controller to present the ad from
#### Properties
| Property | Type | Description |
| - | - | - |
| `delegate` | `AdropInterstitialAdDelegate?` | Delegate (read/write) |
| `isLoaded` | `Bool` | Whether ad is loaded (read-only) |
| `unitId` | `String` | Unit ID (read-only) |
| `txId` | `String` | Transaction ID (read-only) |
| `campaignId` | `String` | Campaign ID (read-only) |
| `creativeId` | `String` | Creative ID (read-only) |
| `isBackfilled` | `Bool` | Whether the ad is a backfill ad (read-only) |
| `destinationURL` | `String` | Ad destination URL (read-only) |
| `browserTarget` | `BrowserTarget?` | How to open URLs on ad click (read-only) |
| `onAdReceived` | `((AdropInterstitialAd) -> Void)?` | Closure callback for ad received |
| `onAdFailedToReceive` | `((AdropInterstitialAd, AdropErrorCode) -> Void)?` | Closure callback for ad failed |
| `onAdImpression` | `((AdropInterstitialAd) -> Void)?` | Closure callback for ad impression |
| `onAdClicked` | `((AdropInterstitialAd) -> Void)?` | Closure callback for ad clicked |
| `onAdWillPresentFullScreen` | `((AdropInterstitialAd) -> Void)?` | Closure callback before presenting |
| `onAdDidPresentFullScreen` | `((AdropInterstitialAd) -> Void)?` | Closure callback after presenting |
| `onAdWillDismissFullScreen` | `((AdropInterstitialAd) -> Void)?` | Closure callback before dismissing |
| `onAdDidDismissFullScreen` | `((AdropInterstitialAd) -> Void)?` | Closure callback after dismissing |
| `onAdFailedToShowFullScreen` | `((AdropInterstitialAd, AdropErrorCode) -> Void)?` | Closure callback for show failure |
**Usage Example:**
```swift theme={null}
let interstitial = AdropInterstitialAd(unitId: "YOUR_UNIT_ID")
interstitial.delegate = self
interstitial.load()
// After ad loads
if interstitial.isLoaded {
interstitial.show(fromRootViewController: self)
}
```
***
### AdropRewardedAd
Class for displaying rewarded ads.
#### Initialization
```swift theme={null}
init(unitId: String)
```
Rewarded ad unit ID created in the Ad Control Console
#### Methods
| Method | Return Type | Description |
| - | - | - |
| `load()` | `void` | Preload ad |
| `show(fromRootViewController:userDidEarnRewardHandler:)` | `void` | Display ad and set reward handler |
##### show Method
```swift theme={null}
func show(
fromRootViewController viewController: UIViewController,
userDidEarnRewardHandler: @escaping (_ type: Int, _ amount: Int) -> Void
)
```
Root view controller to present the ad from
Closure called when user earns a reward. `type` is the reward type and `amount` is the reward amount set in Ad Control Console.
#### Properties
| Property | Type | Description |
| - | - | - |
| `delegate` | `AdropRewardedAdDelegate?` | Delegate (read/write) |
| `isLoaded` | `Bool` | Whether ad is loaded (read-only) |
| `unitId` | `String` | Unit ID (read-only) |
| `txId` | `String` | Transaction ID (read-only) |
| `campaignId` | `String` | Campaign ID (read-only) |
| `creativeId` | `String` | Creative ID (read-only) |
| `isBackfilled` | `Bool` | Whether the ad is a backfill ad (read-only) |
| `destinationURL` | `String` | Ad destination URL (read-only) |
| `browserTarget` | `BrowserTarget?` | How to open URLs on ad click (read-only) |
| `serverSideVerificationOptions` | `AdropServerSideVerificationOptions?` | Server-side verification options (read/write) |
| `onAdReceived` | `((AdropRewardedAd) -> Void)?` | Closure callback for ad received |
| `onAdFailedToReceive` | `((AdropRewardedAd, AdropErrorCode) -> Void)?` | Closure callback for ad failed |
| `onAdImpression` | `((AdropRewardedAd) -> Void)?` | Closure callback for ad impression |
| `onAdClicked` | `((AdropRewardedAd) -> Void)?` | Closure callback for ad clicked |
| `onAdWillPresentFullScreen` | `((AdropRewardedAd) -> Void)?` | Closure callback before presenting |
| `onAdDidPresentFullScreen` | `((AdropRewardedAd) -> Void)?` | Closure callback after presenting |
| `onAdWillDismissFullScreen` | `((AdropRewardedAd) -> Void)?` | Closure callback before dismissing |
| `onAdDidDismissFullScreen` | `((AdropRewardedAd) -> Void)?` | Closure callback after dismissing |
| `onAdFailedToShowFullScreen` | `((AdropRewardedAd, AdropErrorCode) -> Void)?` | Closure callback for show failure |
**Usage Example:**
```swift theme={null}
let rewardedAd = AdropRewardedAd(unitId: "YOUR_UNIT_ID")
rewardedAd.delegate = self
rewardedAd.load()
// After ad loads
if rewardedAd.isLoaded {
rewardedAd.show(fromRootViewController: self) { type, amount in
print("User earned a reward! type: \(type), amount: \(amount)")
// Grant reward logic
}
}
```
***
### AdropPopupAd
Class for displaying popup ads.
#### Initialization
```swift theme={null}
init(unitId: String)
```
Popup ad unit ID created in the Ad Control Console
#### Methods
| Method | Return Type | Description |
| - | - | - |
| `load()` | `void` | Preload ad |
| `show(fromRootViewController:)` | `void` | Display ad |
| `close()` | `void` | Close ad |
| `open(_:useInAppBrowser:)` | `void` | Open URL (defaults to `destinationURL`) |
##### show Method
```swift theme={null}
func show(fromRootViewController viewController: UIViewController)
```
Root view controller to present the ad from
#### Properties
| Property | Type | Description |
| - | - | - |
| `delegate` | `AdropPopupAdDelegate?` | Ad event delegate (read/write) |
| `closeDelegate` | `AdropPopupAdCloseDelegate?` | Close event delegate (read/write) |
| `isLoaded` | `Bool` | Whether ad is loaded (read-only) |
| `unitId` | `String` | Unit ID (read-only) |
| `txId` | `String` | Current transaction ID (read-only) |
| `txIds` | `[String]` | All transaction IDs (read-only) |
| `campaignId` | `String` | Current campaign ID (read-only) |
| `campaignIds` | `[String]` | All campaign IDs (read-only) |
| `creativeId` | `String?` | Current creative ID (read-only) |
| `creativeIds` | `[String]` | All creative IDs (read-only) |
| `creativeType` | `String` | Loaded creative type (`"display"` or `"video"`). Returns `"display"` until an ad response is received. (read-only) |
| `destinationURL` | `String?` | Ad destination URL (read-only) |
| `useCustomClick` | `Bool` | Whether to use custom click handling (read/write) |
| `browserTarget` | `BrowserTarget?` | How to open URLs on ad click (read-only) |
| `backgroundColor` | `UIColor?` | Popup background color (read/write) |
| `hideForTodayTextColor` | `UIColor?` | "Don't show today" text color (read/write) |
| `closeTextColor` | `UIColor?` | Close button text color (read/write) |
| `ctaTextColor` | `UIColor?` | CTA text color (read/write) |
| `onAdReceived` | `((AdropPopupAd) -> Void)?` | Closure callback for ad received |
| `onAdFailedToReceive` | `((AdropPopupAd, AdropErrorCode) -> Void)?` | Closure callback for ad failed |
| `onAdImpression` | `((AdropPopupAd) -> Void)?` | Closure callback for ad impression |
| `onAdClicked` | `((AdropPopupAd) -> Void)?` | Closure callback for ad clicked |
| `onAdWillPresentFullScreen` | `((AdropPopupAd) -> Void)?` | Closure callback before presenting |
| `onAdDidPresentFullScreen` | `((AdropPopupAd) -> Void)?` | Closure callback after presenting |
| `onAdWillDismissFullScreen` | `((AdropPopupAd) -> Void)?` | Closure callback before dismissing |
| `onAdDidDismissFullScreen` | `((AdropPopupAd) -> Void)?` | Closure callback after dismissing |
| `onAdFailedToShowFullScreen` | `((AdropPopupAd, AdropErrorCode) -> Void)?` | Closure callback for show failure |
| `onAdVideoStart` | `((AdropPopupAd) -> Void)?` | Closure callback for video start |
| `onAdVideoEnd` | `((AdropPopupAd) -> Void)?` | Closure callback for video end |
| `onClosed` | `((AdropPopupAd) -> Void)?` | Closure callback for close button |
| `onDimClicked` | `((AdropPopupAd) -> Void)?` | Closure callback for dim area click |
| `onTodayOffClicked` | `((AdropPopupAd) -> Void)?` | Closure callback for "Don't show today" |
**Usage Example:**
```swift theme={null}
let popupAd = AdropPopupAd(unitId: "YOUR_UNIT_ID")
popupAd.delegate = self
popupAd.closeDelegate = self
popupAd.load()
// After ad loads
if popupAd.isLoaded {
popupAd.show(fromRootViewController: self)
}
```
***
### AdropSplashAd
Class for managing splash ads.
`AdropSplashAd` cannot be instantiated directly (the initializer is internal). Use `AdropSplashAdViewController` or `AdropSplashAdView` to display splash ads.
#### Properties
| Property | Type | Description |
| - | - | - |
| `delegate` | `AdropSplashAdDelegate?` | Delegate (read/write) |
| `unitId` | `String` | Unit ID (read-only) |
| `txId` | `String` | Transaction ID (read-only) |
| `campaignId` | `String` | Campaign ID (read-only) |
| `creativeId` | `String` | Creative ID (read-only) |
| `destinationURL` | `String` | Ad destination URL (read-only) |
| `readyToShow` | `Bool` | Whether the ad is ready to display (read-only) |
| `adImage` | `UIImage?` | Cached ad image (read-only) |
***
### AdropSplashAdViewController
View controller for displaying splash ads in full screen.
#### Initialization
```swift theme={null}
init(unitId: String, adRequestTimeout: TimeInterval = 1)
```
Splash ad unit ID created in the Ad Control Console
Ad request timeout in seconds
#### Methods
| Method | Return Type | Description |
| - | - | - |
| `close()` | `void` | Close the splash ad and transition to `mainViewController` |
#### Properties
| Property | Type | Description |
| - | - | - |
| `delegate` | `AdropSplashAdDelegate?` | Delegate (read/write) |
| `splashAd` | `AdropSplashAd` | Underlying splash ad object (read-only) |
| `displayDuration` | `TimeInterval` | Ad display duration (default: 1 second, deprecated — set in console) |
| `logoImage` | `UIImage?` | Logo image to display at bottom (read/write) |
| `backgroundColor` | `UIColor?` | Background color (read/write) |
| `mainViewController` | `UIViewController?` | Main view controller to transition to after splash ad closes (read/write) |
**Usage Example:**
```swift theme={null}
let splashVC = AdropSplashAdViewController(unitId: "YOUR_UNIT_ID")
splashVC.delegate = self
splashVC.displayDuration = 3.0 // Display for 3 seconds
splashVC.logoImage = UIImage(named: "app_logo") // Set logo image
splashVC.modalPresentationStyle = .fullScreen
present(splashVC, animated: false)
```
***
### AdropSplashAdView
View for displaying splash ads. Inherits from `UIImageView`.
#### Initialization
```swift theme={null}
init(unitId: String, adRequestTimeout: TimeInterval = 1)
```
Splash ad unit ID created in the Ad Control Console
Ad request timeout in seconds
Loading happens automatically on initialization. Do not call `load()` directly.
#### Properties
| Property | Type | Description |
| - | - | - |
| `delegate` | `AdropSplashAdViewDelegate?` | Delegate (read/write) |
| `ad` | `AdropSplashAd` | Underlying splash ad object (read-only) |
| `unitId` | `String` | Unit ID (read-only) |
| `txId` | `String?` | Transaction ID (read-only) |
| `creativeId` | `String` | Creative ID (read-only) |
| `isClosed` | `Bool` | Whether the splash ad has closed (read-only) |
| `displayDuration` | `TimeInterval` | Ad display duration (deprecated — set in console) |
***
### AdropMetrics
Class for managing user properties and events.
#### Static Properties
| Property | Type | Description |
| - | - | - |
| `userDataConsent` | `Bool` | User data consent status (default: `true`, read-only) |
#### Static Methods
| Method | Return Type | Description |
| - | - | - |
| `setUserDataConsent(_:)` | `void` | Set user data collection consent (`true` = consent, `false` = no consent). When set to `false`, data is stored locally but not sent to the server. |
| `setProperty(key:value:)` | `void` | Set user property |
| `properties()` | `[String: Encodable]` | Get all stored user properties |
| `sendEvent(name:params:)` | `void` | Send user event |
| `logEvent(name:params:)` | `void` | **Deprecated.** Use `sendEvent` instead. |
#### Set User Property
```swift theme={null}
static func setProperty(key: String, value: Encodable?)
```
Property key (max 64 characters)
Property value (supports String, Int, Double, Bool)
**Usage Example:**
```swift theme={null}
AdropMetrics.setProperty(key: "age", value: 25)
AdropMetrics.setProperty(key: "gender", value: "male")
AdropMetrics.setProperty(key: "isPremium", value: true)
```
#### Get Properties
```swift theme={null}
static func properties() -> [String: Encodable]
```
Returns all user properties currently stored via `setProperty(key:value:)`.
**Usage Example:**
```swift theme={null}
let stored = AdropMetrics.properties()
print(stored)
```
#### Send Event
```swift theme={null}
static func sendEvent(name: String, params: [String: Any]? = nil)
```
Event name (1–64 characters)
Event parameters dictionary. Keys max 64 characters, string values max 1024 characters.
**Usage Example:**
```swift theme={null}
// Simple event
AdropMetrics.sendEvent(name: "app_open")
// Event with parameters
AdropMetrics.sendEvent(name: "view_item", params: [
"item_id": "SKU-123",
"item_name": "Widget",
"price": 29.99
])
// Multi-item event (purchase)
AdropMetrics.sendEvent(name: "purchase", params: [
"tx_id": "tx_123",
"currency": "KRW",
"items": [
["item_id": "A", "item_name": "Product A", "price": 100, "quantity": 1],
["item_id": "B", "item_name": "Product B", "price": 200, "quantity": 2]
]
])
```
`logEvent()` is deprecated and will be removed in a future version. Use `sendEvent()` instead.
* Event name must be 1–64 characters.
* Duplicate events with the same name and parameters are throttled (500ms window).
* The SDK must be initialized before calling `sendEvent()`.
#### Log Event (Deprecated)
```swift theme={null}
@available(*, deprecated, renamed: "sendEvent(name:params:)")
static func logEvent(name: String, params: [String: Encodable]? = [:])
```
This method is deprecated. Use `sendEvent(name:params:)` instead.
***
## Protocols (Delegates)
### AdropBannerDelegate
Protocol for handling banner ad lifecycle events.
#### Required Methods
```swift theme={null}
func onAdReceived(_ banner: AdropBanner)
```
Called when ad is received successfully.
```swift theme={null}
func onAdFailedToReceive(_ banner: AdropBanner, _ errorCode: AdropErrorCode)
```
Called when ad fails to receive.
#### Optional Methods
```swift theme={null}
optional func onAdImpression(_ banner: AdropBanner)
```
Called when ad is displayed on screen.
```swift theme={null}
optional func onAdClicked(_ banner: AdropBanner)
```
Called when user clicks the ad.
```swift theme={null}
optional func onAdsReceived(_ banners: [AdropBanner])
```
Called when a batch of banner ads is received from `AdropBanner.loads(...)`.
```swift theme={null}
optional func onAdsFailedToReceive(_ errorCode: AdropErrorCode)
```
Called when a batch load from `AdropBanner.loads(...)` fails.
```swift theme={null}
optional func onAdVideoStart(_ banner: AdropBanner)
```
Called when the video ad starts playing.
```swift theme={null}
optional func onAdVideoEnd(_ banner: AdropBanner)
```
Called when the video ad finishes playing.
**Implementation Example:**
```swift theme={null}
extension ViewController: AdropBannerDelegate {
func onAdReceived(_ banner: AdropBanner) {
print("Banner ad received")
}
func onAdFailedToReceive(_ banner: AdropBanner, _ errorCode: AdropErrorCode) {
print("Banner ad failed: \(errorCode)")
}
func onAdImpression(_ banner: AdropBanner) {
print("Banner ad impression")
}
func onAdClicked(_ banner: AdropBanner) {
print("Banner ad clicked")
}
}
```
***
### AdropNativeAdDelegate
Protocol for handling native ad lifecycle events.
#### Required Methods
```swift theme={null}
func onAdReceived(_ ad: AdropNativeAd)
```
Called when ad is received successfully.
```swift theme={null}
func onAdFailedToReceive(_ ad: AdropNativeAd, _ errorCode: AdropErrorCode)
```
Called when ad fails to receive.
#### Optional Methods
```swift theme={null}
optional func onAdImpression(_ ad: AdropNativeAd)
```
Called when ad is displayed on screen.
```swift theme={null}
optional func onAdClicked(_ ad: AdropNativeAd)
```
Called when user clicks the ad.
```swift theme={null}
optional func onAdsReceived(_ ads: [AdropNativeAd])
```
Called when a batch of native ads is received from `AdropNativeAd.loads(...)`.
```swift theme={null}
optional func onAdsFailedToReceive(_ errorCode: AdropErrorCode)
```
Called when a batch load from `AdropNativeAd.loads(...)` fails.
```swift theme={null}
optional func onAdVideoStart(_ ad: AdropNativeAd)
```
Called when the video ad starts playing.
```swift theme={null}
optional func onAdVideoEnd(_ ad: AdropNativeAd)
```
Called when the video ad finishes playing.
***
### AdropInterstitialAdDelegate
Protocol for handling interstitial ad lifecycle events.
#### Required Methods
```swift theme={null}
func onAdReceived(_ ad: AdropInterstitialAd)
```
Called when ad is received successfully.
```swift theme={null}
func onAdFailedToReceive(_ ad: AdropInterstitialAd, _ errorCode: AdropErrorCode)
```
Called when ad fails to receive.
#### Optional Methods
```swift theme={null}
optional func onAdImpression(_ ad: AdropInterstitialAd)
```
Called when ad is displayed on screen.
```swift theme={null}
optional func onAdClicked(_ ad: AdropInterstitialAd)
```
Called when user clicks the ad.
```swift theme={null}
optional func onAdWillPresentFullScreen(_ ad: AdropInterstitialAd)
```
Called just before the ad is displayed in full screen.
```swift theme={null}
optional func onAdDidPresentFullScreen(_ ad: AdropInterstitialAd)
```
Called immediately after the ad is displayed in full screen.
```swift theme={null}
optional func onAdWillDismissFullScreen(_ ad: AdropInterstitialAd)
```
Called just before the ad is dismissed.
```swift theme={null}
optional func onAdDidDismissFullScreen(_ ad: AdropInterstitialAd)
```
Called when ad is closed.
```swift theme={null}
optional func onAdFailedToShowFullScreen(_ ad: AdropInterstitialAd, _ errorCode: AdropErrorCode)
```
Called when ad fails to show.
***
### AdropRewardedAdDelegate
Protocol for handling rewarded ad lifecycle events.
#### Required Methods
```swift theme={null}
func onAdReceived(_ ad: AdropRewardedAd)
```
Called when ad is received successfully.
```swift theme={null}
func onAdFailedToReceive(_ ad: AdropRewardedAd, _ errorCode: AdropErrorCode)
```
Called when ad fails to receive.
#### Optional Methods
```swift theme={null}
optional func onAdImpression(_ ad: AdropRewardedAd)
```
Called when ad is displayed on screen.
```swift theme={null}
optional func onAdClicked(_ ad: AdropRewardedAd)
```
Called when user clicks the ad.
```swift theme={null}
optional func onAdWillPresentFullScreen(_ ad: AdropRewardedAd)
```
Called just before the ad is displayed in full screen.
```swift theme={null}
optional func onAdDidPresentFullScreen(_ ad: AdropRewardedAd)
```
Called immediately after the ad is displayed in full screen.
```swift theme={null}
optional func onAdWillDismissFullScreen(_ ad: AdropRewardedAd)
```
Called just before the ad is dismissed.
```swift theme={null}
optional func onAdDidDismissFullScreen(_ ad: AdropRewardedAd)
```
Called when ad is closed.
```swift theme={null}
optional func onAdFailedToShowFullScreen(_ ad: AdropRewardedAd, _ errorCode: AdropErrorCode)
```
Called when ad fails to show.
***
### AdropPopupAdDelegate
Protocol for handling popup ad lifecycle events.
#### Required Methods
```swift theme={null}
func onAdReceived(_ ad: AdropPopupAd)
```
Called when ad is received successfully.
```swift theme={null}
func onAdFailedToReceive(_ ad: AdropPopupAd, _ errorCode: AdropErrorCode)
```
Called when ad fails to receive.
#### Optional Methods
```swift theme={null}
optional func onAdImpression(_ ad: AdropPopupAd)
```
Called when ad is displayed on screen.
```swift theme={null}
optional func onAdClicked(_ ad: AdropPopupAd)
```
Called when user clicks the ad.
```swift theme={null}
optional func onAdWillPresentFullScreen(_ ad: AdropPopupAd)
```
Called just before the popup ad is displayed.
```swift theme={null}
optional func onAdDidPresentFullScreen(_ ad: AdropPopupAd)
```
Called immediately after the popup ad is displayed.
```swift theme={null}
optional func onAdWillDismissFullScreen(_ ad: AdropPopupAd)
```
Called just before the popup ad is dismissed.
```swift theme={null}
optional func onAdDidDismissFullScreen(_ ad: AdropPopupAd)
```
Called when ad is closed.
```swift theme={null}
optional func onAdFailedToShowFullScreen(_ ad: AdropPopupAd, _ errorCode: AdropErrorCode)
```
Called when ad fails to show.
```swift theme={null}
optional func onAdVideoStart(_ ad: AdropPopupAd)
```
Called when the video ad starts playing.
```swift theme={null}
optional func onAdVideoEnd(_ ad: AdropPopupAd)
```
Called when the video ad finishes playing.
***
### AdropPopupAdCloseDelegate
Protocol for handling popup ad close events.
#### Optional Methods
```swift theme={null}
optional func onClosed(_ ad: AdropPopupAd)
```
Called when user clicks the close button.
```swift theme={null}
optional func onDimClicked(_ ad: AdropPopupAd)
```
Called when user clicks outside the popup (dim area).
```swift theme={null}
optional func onTodayOffClicked(_ ad: AdropPopupAd)
```
Called when user selects "Don't show today".
***
### AdropSplashAdDelegate
Protocol for handling splash ad lifecycle events. All methods are `@objc optional`.
#### Optional Methods
```swift theme={null}
optional func onAdReceived(_ ad: AdropSplashAd)
```
Called when ad is received successfully.
```swift theme={null}
optional func onAdFailedToReceive(_ ad: AdropSplashAd, _ errorCode: AdropErrorCode)
```
Called when ad fails to receive.
```swift theme={null}
optional func onAdImpression(_ ad: AdropSplashAd)
```
Called when ad is displayed on screen.
```swift theme={null}
optional func onAdClose(_ ad: AdropSplashAd, impressed: Bool)
```
Called when the splash ad is closed. `impressed` indicates whether the ad was fully displayed.
***
### AdropSplashAdViewDelegate
Protocol for handling splash ad view lifecycle events.
#### Required Methods
```swift theme={null}
func onAdClose(_ adView: AdropSplashAdView, impressed: Bool)
```
Called when the splash ad view is closed. `impressed` indicates whether the ad was fully displayed.
#### Optional Methods
```swift theme={null}
optional func onAdReceived(_ adView: AdropSplashAdView)
```
Called when ad is received successfully.
```swift theme={null}
optional func onAdFailedToReceive(_ adView: AdropSplashAdView, _ errorCode: AdropErrorCode)
```
Called when ad fails to receive.
```swift theme={null}
optional func onAdImpression(_ adView: AdropSplashAdView)
```
Called when ad is displayed on screen.
***
### UseCustomClick
Protocol for handling custom ad click behavior. Implemented by `AdropBanner`, `AdropNativeAd`, and `AdropPopupAd`.
| Property / Method | Type | Description |
| - | - | - |
| `destinationURL` | `String?` | The destination URL of the ad (read-only) |
| `useCustomClick` | `Bool` | When `true`, disables automatic URL opening on ad click |
| `open(_:useInAppBrowser:)` | Method | Opens a URL manually. Call this in click callback when `useCustomClick` is `true` |
***
## Enums
### AdropErrorCode
Error codes that can occur during ad loading and display.
```swift theme={null}
enum AdropErrorCode: Int
```
| Case | Code Value | Description |
| - | - | - |
| `ERROR_CODE_NETWORK` | 0 | Network error |
| `ERROR_CODE_INTERNAL` | 1 | Internal error |
| `ERROR_CODE_INITIALIZE` | 2 | SDK initialization failed |
| `ERROR_CODE_INVALID_UNIT` | 3 | Invalid unit ID |
| `ERROR_CODE_NOT_TARGET_COUNTRY` | 4 | Not a target country |
| `ERROR_CODE_AD_INACTIVE` | 5 | No active campaigns |
| `ERROR_CODE_AD_NO_FILL` | 6 | No ads available to display |
| `ERROR_CODE_AD_LOAD_DUPLICATED` | 7 | Duplicate ad load request |
| `ERROR_CODE_AD_LOADING` | 8 | Ad is loading |
| `ERROR_CODE_AD_EMPTY` | 9 | Ad not loaded |
| `ERROR_CODE_AD_SHOWN` | 10 | Ad already shown |
| `ERROR_CODE_AD_HIDE_FOR_TODAY` | 11 | "Don't show today" is set |
| `ERROR_CODE_ACCOUNT_USAGE_LIMIT_EXCEEDED` | 12 | Account usage limit exceeded |
| `ERROR_CODE_LANDSCAPE_UNSUPPORTED` | 13 | Landscape mode not supported |
| `ERROR_CODE_AD_BACKFILL_NO_FILL` | 14 | No backfill ads available |
#### Error Code Descriptions
**Network Related**
* `ERROR_CODE_NETWORK`: Network connection error occurred. Check internet connection.
**Initialization Related**
* `ERROR_CODE_INITIALIZE`: SDK is not initialized. Call `Adrop.initialize()` first.
**Unit Related**
* `ERROR_CODE_INVALID_UNIT`: Invalid unit ID. Verify in the console.
* `ERROR_CODE_NOT_TARGET_COUNTRY`: Current user's country is not a target country.
**Ad Related**
* `ERROR_CODE_AD_INACTIVE`: No active campaigns for this unit.
* `ERROR_CODE_AD_NO_FILL`: No ads available to display.
* `ERROR_CODE_AD_BACKFILL_NO_FILL`: No backfill ads available either.
**Ad State Related**
* `ERROR_CODE_AD_LOAD_DUPLICATED`: Already loading an ad.
* `ERROR_CODE_AD_LOADING`: Ad is still loading.
* `ERROR_CODE_AD_EMPTY`: Must load ad before displaying.
* `ERROR_CODE_AD_SHOWN`: Ad already shown. Load a new ad.
* `ERROR_CODE_AD_HIDE_FOR_TODAY`: User selected "Don't show today".
**Other**
* `ERROR_CODE_INTERNAL`: Internal error occurred.
* `ERROR_CODE_ACCOUNT_USAGE_LIMIT_EXCEEDED`: Account ad usage limit exceeded.
* `ERROR_CODE_LANDSCAPE_UNSUPPORTED`: This ad does not support landscape mode.
**Usage Example:**
```swift theme={null}
func onAdFailedToReceive(_ banner: AdropBanner, _ errorCode: AdropErrorCode) {
switch errorCode {
case .ERROR_CODE_NETWORK:
print("Network error: Check your connection")
case .ERROR_CODE_AD_NO_FILL:
print("No ads available to display")
case .ERROR_CODE_INVALID_UNIT:
print("Invalid unit ID")
default:
print("Ad load failed: \(errorCode.rawValue)")
}
}
```
***
### AdropTheme
Enum representing app theme settings.
```swift theme={null}
enum AdropTheme
```
| Case | Description |
| - | - |
| `light` | Light mode |
| `dark` | Dark mode |
| `auto` | Follow system setting (default) |
**Usage Example:**
```swift theme={null}
// Set light mode
Adrop.setTheme(.light)
// Set dark mode
Adrop.setTheme(.dark)
// Follow system setting
Adrop.setTheme(.auto)
```
***
### BrowserTarget
Controls how ad URLs are opened.
| Value | Description |
| - | - |
| `EXTERNAL` | Opens URL in the default external browser (default) |
| `INTERNAL` | Opens URL in an in-app browser |
***
## Structs and Types
### AdropNativeAdProfile
Struct containing advertiser profile information.
```swift theme={null}
struct AdropNativeAdProfile {
let displayLogo: String // Advertiser logo URL
let displayName: String // Advertiser name
let link: String? // Advertiser profile link
}
```
**Usage Example:**
```swift theme={null}
let profile = nativeAd.profile
profileNameLabel.text = profile.displayName
loadImage(from: profile.displayLogo, into: profileImageView)
```
### AdropServerSideVerificationOptions
Class for setting server-side verification options for rewarded ads.
```swift theme={null}
class AdropServerSideVerificationOptions: NSObject {
var userId: String?
var customData: String?
init(userId: String? = nil, customData: String? = nil)
}
```
| Property | Type | Description |
| - | - | - |
| `userId` | `String?` | User identifier for server-side verification |
| `customData` | `String?` | Custom data string to include in the server callback |
**Usage Example:**
```swift theme={null}
let options = AdropServerSideVerificationOptions(
userId: "user_12345",
customData: "level_5_reward"
)
rewardedAd.serverSideVerificationOptions = options
```
***
## Constants
### AdropUnitId
Test unit ID constants.
```swift theme={null}
struct AdropUnitId {
// Banner ads
static let PUBLIC_TEST_UNIT_ID_320_50: String
static let PUBLIC_TEST_UNIT_ID_320_100: String
static let PUBLIC_TEST_UNIT_ID_CAROUSEL: String
static let PUBLIC_TEST_UNIT_ID_BANNER_VIDEO_16_9: String
static let PUBLIC_TEST_UNIT_ID_BANNER_VIDEO_9_16: String
// Native ads
static let PUBLIC_TEST_UNIT_ID_NATIVE: String
static let PUBLIC_TEST_UNIT_ID_NATIVE_VIDEO_16_9: String
static let PUBLIC_TEST_UNIT_ID_NATIVE_VIDEO_9_16: String
// Interstitial ads
static let PUBLIC_TEST_UNIT_ID_INTERSTITIAL: String
// Rewarded ads
static let PUBLIC_TEST_UNIT_ID_REWARDED: String
// Popup ads
static let PUBLIC_TEST_UNIT_ID_POPUP_BOTTOM: String
static let PUBLIC_TEST_UNIT_ID_POPUP_CENTER: String
static let PUBLIC_TEST_UNIT_ID_POPUP_BOTTOM_VIDEO_16_9: String
static let PUBLIC_TEST_UNIT_ID_POPUP_BOTTOM_VIDEO_9_16: String
static let PUBLIC_TEST_UNIT_ID_POPUP_CENTER_VIDEO_16_9: String
static let PUBLIC_TEST_UNIT_ID_POPUP_CENTER_VIDEO_9_16: String
// Splash ads
static let PUBLIC_TEST_UNIT_ID_SPLASH: String
}
```
**Usage Example:**
```swift theme={null}
let banner = AdropBanner(unitId: AdropUnitId.PUBLIC_TEST_UNIT_ID_320_100)
let carouselBanner = AdropBanner(unitId: AdropUnitId.PUBLIC_TEST_UNIT_ID_CAROUSEL)
let nativeAd = AdropNativeAd(unitId: AdropUnitId.PUBLIC_TEST_UNIT_ID_NATIVE)
let interstitial = AdropInterstitialAd(unitId: AdropUnitId.PUBLIC_TEST_UNIT_ID_INTERSTITIAL)
let popup = AdropPopupAd(unitId: AdropUnitId.PUBLIC_TEST_UNIT_ID_POPUP_CENTER)
```
Use test unit IDs only in development environments. You must use actual unit IDs created in the console for production.
***
## Backfill Revenue (`onPaidEvent`)
Callback that delivers revenue information when a backfill ad is served. It fires only for backfill ads and is never invoked for Adrop direct ads.
### Supported ad formats
Supported on Banner, Native, Interstitial, and Rewarded. Not supported on Popup or Splash.
### Delegate / closure
```swift theme={null}
@objc optional func onPaidEvent(_ ad: AdropBanner, _ value: AdropAdValue)
// or as a closure property
public var onPaidEvent: ((AdropBanner, AdropAdValue) -> Void)?
```
### `AdropAdValue`
| Field | Type | Description |
| - | - | - |
| `network` | `String` | Backfill network identifier (e.g., `admob`) |
| `adSourceName` | `String?` | Mediation source inside the network (e.g., `AppLovin`) |
| `valueMicros` | `Int64` | Revenue in micro units (1/1,000,000 of `currencyCode`) |
| `currencyCode` | `String` | ISO 4217 currency code |
| `precision` | `AdropAdValuePrecision` | Precision (`unknown`, `estimated`, `publisherProvided`, `precise`) |
| `value` | `NSDecimalNumber` | Computed as `valueMicros × 10⁻⁶` |
### Usage
```swift theme={null}
let banner = AdropBanner(unitId: "YOUR_UNIT_ID")
banner.delegate = self
extension YourController: AdropBannerDelegate {
func onPaidEvent(_ banner: AdropBanner, _ value: AdropAdValue) {
analytics.track(banner.unitId, value.valueMicros, value.currencyCode)
}
}
```
`AdropNativeAd`, `AdropInterstitialAd`, and `AdropRewardedAd` register the same way via delegate method or the `onPaidEvent` closure property.
***
## Additional Resources
Banner ad implementation guide
Native ad implementation guide
Interstitial ad implementation guide
Rewarded ad implementation guide
# Rewarded Ads
Source: https://docs.adrop.io/sdk/ios/rewarded
Guide to implementing rewarded video ads in iOS apps.
## Overview
Rewarded ads are full-screen video ads that provide rewards such as in-game currency, lives, or hints when users watch the video to completion.
### Key Features
* Full-screen video ads
* Rewards only provided when user completely watches the ad
* User chooses to watch ad (e.g., "Watch video to get lives" button)
* Configurable reward type and amount
Use test unit ID in development environment: `PUBLIC_TEST_UNIT_ID_REWARDED`
***
## Implementation Steps
Implement rewarded ads in the following steps:
1. **Create AdropRewardedAd Instance** - Create ad object with unit ID
2. **Set Delegate** - Set delegate to handle ad events
3. **Load Ad** - Request ad using `load()` method
4. **Show Ad** - Display ad using `show()` method and handle rewards
***
## Swift Example
### Basic Implementation
```swift theme={null}
import UIKit
import AdropAds
class RewardedViewController: UIViewController {
private var rewardedAd: AdropRewardedAd?
override func viewDidLoad() {
super.viewDidLoad()
// 1. Create AdropRewardedAd instance
rewardedAd = AdropRewardedAd(unitId: "YOUR_UNIT_ID")
// 2. Set delegate
rewardedAd?.delegate = self
// 3. Load ad
rewardedAd?.load()
}
@IBAction func showRewardedAdButtonTapped(_ sender: UIButton) {
// Check if ad is loaded
guard rewardedAd?.isLoaded == true else {
print("Ad not loaded yet")
return
}
// 4. Show ad and handle reward
rewardedAd?.show(fromRootViewController: self) { [weak self] type, amount in
// Grant reward
print("Reward earned - Type: \(type), Amount: \(amount)")
self?.grantReward(type: type, amount: amount)
}
}
private func grantReward(type: Int, amount: Int) {
// Grant reward to user
// Example: Add game currency, restore lives, etc.
// Important: It is recommended to verify rewards on your server
// Processing rewards only on the client side allows cheating
}
}
// MARK: - AdropRewardedAdDelegate
extension RewardedViewController: AdropRewardedAdDelegate {
// Required: Ad received successfully
func onAdReceived(_ ad: AdropRewardedAd) {
print("Ad received")
// Enable watch ad button, etc.
}
// Required: Ad receive failed
func onAdFailedToReceive(_ ad: AdropRewardedAd, _ errorCode: AdropErrorCode) {
print("Ad receive failed: \(errorCode)")
// Keep watch ad button disabled, etc.
}
// Optional: Ad impression
func onAdImpression(_ ad: AdropRewardedAd) {
print("Ad impression")
}
// Optional: Ad clicked
func onAdClicked(_ ad: AdropRewardedAd) {
print("Ad clicked")
}
// Optional: Before showing full screen ad
func onAdWillPresentFullScreen(_ ad: AdropRewardedAd) {
print("About to show ad")
// Pause background music, etc.
}
// Optional: After showing full screen ad
func onAdDidPresentFullScreen(_ ad: AdropRewardedAd) {
print("Ad shown")
}
// Optional: Before dismissing full screen ad
func onAdWillDismissFullScreen(_ ad: AdropRewardedAd) {
print("About to dismiss ad")
}
// Optional: After dismissing full screen ad
func onAdDidDismissFullScreen(_ ad: AdropRewardedAd) {
print("Ad dismissed")
// Resume background music, etc.
// Preload next ad
rewardedAd?.load()
}
// Optional: Ad show failed
func onAdFailedToShowFullScreen(_ ad: AdropRewardedAd, _ errorCode: AdropErrorCode) {
print("Ad show failed: \(errorCode)")
}
}
```
### SwiftUI Example
```swift theme={null}
import SwiftUI
import AdropAds
class RewardedAdViewModel: NSObject, ObservableObject, AdropRewardedAdDelegate {
@Published var isAdReady = false
@Published var showingAlert = false
@Published var alertMessage = ""
private var rewardedAd: AdropRewardedAd?
override init() {
super.init()
loadAd()
}
func loadAd() {
rewardedAd = AdropRewardedAd(unitId: "YOUR_UNIT_ID")
rewardedAd?.delegate = self
rewardedAd?.load()
}
func showAd() {
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let rootViewController = windowScene.windows.first?.rootViewController else {
return
}
rewardedAd?.show(fromRootViewController: rootViewController) { [weak self] type, amount in
self?.alertMessage = "Reward earned! Type: \(type), Amount: \(amount)"
self?.showingAlert = true
self?.grantReward(type: type, amount: amount)
}
}
private func grantReward(type: Int, amount: Int) {
// Grant reward
print("Granting reward - Type: \(type), Amount: \(amount)")
}
// MARK: - AdropRewardedAdDelegate
func onAdReceived(_ ad: AdropRewardedAd) {
isAdReady = true
}
func onAdFailedToReceive(_ ad: AdropRewardedAd, _ errorCode: AdropErrorCode) {
isAdReady = false
alertMessage = "Ad load failed: \(errorCode)"
showingAlert = true
}
func onAdDidDismissFullScreen(_ ad: AdropRewardedAd) {
isAdReady = false
// Preload next ad
loadAd()
}
func onAdFailedToShowFullScreen(_ ad: AdropRewardedAd, _ errorCode: AdropErrorCode) {
alertMessage = "Ad show failed: \(errorCode)"
showingAlert = true
}
}
struct RewardedAdView: View {
@StateObject private var viewModel = RewardedAdViewModel()
var body: some View {
VStack(spacing: 20) {
Text("Rewarded Ad Example")
.font(.title)
Button(action: {
viewModel.showAd()
}) {
Text("Watch video to get reward")
.padding()
.background(viewModel.isAdReady ? Color.blue : Color.gray)
.foregroundColor(.white)
.cornerRadius(8)
}
.disabled(!viewModel.isAdReady)
}
.alert(isPresented: $viewModel.showingAlert) {
Alert(
title: Text("Notification"),
message: Text(viewModel.alertMessage),
dismissButton: .default(Text("OK"))
)
}
}
}
```
***
## Objective-C Example
```objc theme={null}
@import AdropAds;
@interface RewardedViewController ()
@property (nonatomic, strong) AdropRewardedAd *rewardedAd;
@end
@implementation RewardedViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 1. Create AdropRewardedAd instance
self.rewardedAd = [[AdropRewardedAd alloc] initWithUnitId:@"YOUR_UNIT_ID"];
// 2. Set delegate
self.rewardedAd.delegate = self;
// 3. Load ad
[self.rewardedAd load];
}
- (IBAction)showRewardedAdButtonTapped:(UIButton *)sender {
// Check if ad is loaded
if (!self.rewardedAd.isLoaded) {
NSLog(@"Ad not loaded yet");
return;
}
// 4. Show ad and handle reward
__weak typeof(self) weakSelf = self;
[self.rewardedAd showFromRootViewController:self
userDidEarnRewardHandler:^(NSInteger type, NSInteger amount) {
NSLog(@"Reward earned - Type: %ld, Amount: %ld", (long)type, (long)amount);
[weakSelf grantRewardWithType:type amount:amount];
}];
}
- (void)grantRewardWithType:(NSInteger)type amount:(NSInteger)amount {
// Grant reward to user
}
#pragma mark - AdropRewardedAdDelegate
// Required: Ad received successfully
- (void)onAdReceived:(AdropRewardedAd *)ad {
NSLog(@"Ad received");
}
// Required: Ad receive failed
- (void)onAdFailedToReceive:(AdropRewardedAd *)ad :(AdropErrorCode)errorCode {
NSLog(@"Ad receive failed: %ld", (long)errorCode);
}
// Optional: Ad impression
- (void)onAdImpression:(AdropRewardedAd *)ad {
NSLog(@"Ad impression");
}
// Optional: Ad clicked
- (void)onAdClicked:(AdropRewardedAd *)ad {
NSLog(@"Ad clicked");
}
// Optional: Before showing full screen ad
- (void)onAdWillPresentFullScreen:(AdropRewardedAd *)ad {
NSLog(@"About to show ad");
}
// Optional: After showing full screen ad
- (void)onAdDidPresentFullScreen:(AdropRewardedAd *)ad {
NSLog(@"Ad shown");
}
// Optional: Before dismissing full screen ad
- (void)onAdWillDismissFullScreen:(AdropRewardedAd *)ad {
NSLog(@"About to dismiss ad");
}
// Optional: After dismissing full screen ad
- (void)onAdDidDismissFullScreen:(AdropRewardedAd *)ad {
NSLog(@"Ad dismissed");
// Preload next ad
[self.rewardedAd load];
}
// Optional: Ad show failed
- (void)onAdFailedToShowFullScreen:(AdropRewardedAd *)ad :(AdropErrorCode)errorCode {
NSLog(@"Ad show failed: %ld", (long)errorCode);
}
@end
```
***
## Delegate Methods
### Required Methods
Called when ad is received successfully. You can show the ad using `show()` method from this point.
Called when ad fails to load. You can check the cause of failure through the error code.
### Optional Methods
Called when ad impression is recorded and sent.
Called when user clicks the ad.
Called just before the ad screen is displayed. You can pause background music, etc.
Called after the ad screen is fully displayed.
Called just before the ad screen is dismissed.
Called after the ad screen is fully dismissed. You can resume background music or preload the next ad.
Called when ad fails to show. You can check the cause of failure through the error code.
***
## Closure Callbacks
As an alternative to delegates, you can use closure-based callbacks.
```swift theme={null}
let rewardedAd = AdropRewardedAd(unitId: "YOUR_UNIT_ID")
// Required
rewardedAd.onAdReceived = { ad in
print("Ad received")
}
rewardedAd.onAdFailedToReceive = { ad, errorCode in
print("Ad failed: \(errorCode)")
}
// Optional
rewardedAd.onAdImpression = { ad in print("Ad impression") }
rewardedAd.onAdClicked = { ad in print("Ad clicked") }
rewardedAd.onAdWillPresentFullScreen = { ad in print("Ad will present") }
rewardedAd.onAdDidPresentFullScreen = { ad in print("Ad did present") }
rewardedAd.onAdWillDismissFullScreen = { ad in print("Ad will dismiss") }
rewardedAd.onAdDidDismissFullScreen = { ad in print("Ad did dismiss") }
rewardedAd.onAdFailedToShowFullScreen = { ad, errorCode in print("Ad failed to show: \(errorCode)") }
```
If both a delegate and closures are set, both will be called.
***
## Reward Handling
### Reward Callback
Pass the reward callback through the `userDidEarnRewardHandler` parameter of the `show()` method.
```swift theme={null}
rewardedAd?.show(fromRootViewController: self) { type, amount in
// type: Reward type (Int)
// amount: Reward amount (Int)
print("Reward earned - Type: \(type), Amount: \(amount)")
}
```
### Reward Type and Amount
* **type**: Reward type set in Ad Control Console (integer value)
* **amount**: Reward amount set in Ad Control Console (integer value)
## Server-Side Verification
Server-side verification (SSV) allows you to verify reward grants on your server, preventing client-side manipulation. Use `AdropServerSideVerificationOptions` to pass user identification and custom data that will be included in the server-to-server callback.
To receive SSV callbacks on your server, register a callback URL in the console: **\[Settings] > \[Integrations] > Reward Ad SSV**. The `userId` and `customData` set here are included in the encrypted payload sent to that URL. See the [Reward Ad SSV guide](/integrations/ssv) for payload decryption and setup.
### Setting Up SSV
```swift theme={null}
let rewardedAd = AdropRewardedAd(unitId: "YOUR_UNIT_ID")
rewardedAd.delegate = self
// Set server-side verification options before loading the ad
let ssvOptions = AdropServerSideVerificationOptions(
userId: "user_12345",
customData: "extra_info"
)
rewardedAd.serverSideVerificationOptions = ssvOptions
rewardedAd.load()
```
### AdropServerSideVerificationOptions
| Property | Type | Description |
| - | - | - |
| `userId` | `String?` | User identifier for server-side verification |
| `customData` | `String?` | Custom data string to include in the callback |
When the ad falls back to backfill, `customData` is sanitized to base64url before being forwarded (`+`→`-`, `/`→`_`, `=` stripped; backfill 1.12.1+). If you pack a base64 payload into `customData`, decode it as base64url on your server.
```swift theme={null}
// Initialize with parameters
let options = AdropServerSideVerificationOptions(
userId: "user_12345",
customData: "level_5_reward"
)
// Or set properties individually
let options = AdropServerSideVerificationOptions()
options.userId = "user_12345"
options.customData = "level_5_reward"
rewardedAd.serverSideVerificationOptions = options
```
Set `serverSideVerificationOptions` before calling `load()`. The `userId` and `customData` values are sent to your server callback URL when the user earns a reward.
***
## Best Practices
### 1. Preload Ads
Preload ads for better user experience.
```swift theme={null}
class GameViewController: UIViewController {
private var rewardedAd: AdropRewardedAd?
override func viewDidLoad() {
super.viewDidLoad()
// Preload ad when entering screen
loadRewardedAd()
}
private func loadRewardedAd() {
rewardedAd = AdropRewardedAd(unitId: "YOUR_UNIT_ID")
rewardedAd?.delegate = self
rewardedAd?.load()
}
private func showRewardedAd() {
rewardedAd?.show(fromRootViewController: self) { [weak self] type, amount in
self?.grantReward(type: type, amount: amount)
}
}
}
extension GameViewController: AdropRewardedAdDelegate {
func onAdReceived(_ ad: AdropRewardedAd) {
print("Rewarded ad ready")
}
func onAdFailedToReceive(_ ad: AdropRewardedAd, _ errorCode: AdropErrorCode) {
print("Ad load failed: \(errorCode)")
}
func onAdDidDismissFullScreen(_ ad: AdropRewardedAd) {
// Preload next ad after ad is dismissed
loadRewardedAd()
}
}
```
### 2. Check Ad Ready State
Check if ad is loaded and update UI.
```swift theme={null}
class GameViewController: UIViewController {
@IBOutlet weak var watchAdButton: UIButton!
private var rewardedAd: AdropRewardedAd?
private var isAdReady = false
private func updateButtonState() {
watchAdButton.isEnabled = isAdReady
watchAdButton.alpha = isAdReady ? 1.0 : 0.5
watchAdButton.setTitle(
isAdReady ? "Watch video to get lives" : "Loading ad...",
for: .normal
)
}
}
extension GameViewController: AdropRewardedAdDelegate {
func onAdReceived(_ ad: AdropRewardedAd) {
isAdReady = true
updateButtonState()
}
func onAdFailedToReceive(_ ad: AdropRewardedAd, _ errorCode: AdropErrorCode) {
isAdReady = false
updateButtonState()
}
func onAdDidDismissFullScreen(_ ad: AdropRewardedAd) {
isAdReady = false
updateButtonState()
// Load next ad
rewardedAd?.load()
}
}
```
### 3. Background Music Handling
Control background music appropriately before and after showing ads.
```swift theme={null}
extension GameViewController: AdropRewardedAdDelegate {
func onAdReceived(_ ad: AdropRewardedAd) { }
func onAdFailedToReceive(_ ad: AdropRewardedAd, _ errorCode: AdropErrorCode) { }
func onAdWillPresentFullScreen(_ ad: AdropRewardedAd) {
// Pause background music before showing ad
AudioManager.shared.pauseBackgroundMusic()
}
func onAdDidDismissFullScreen(_ ad: AdropRewardedAd) {
// Resume background music after ad ends
AudioManager.shared.resumeBackgroundMusic()
}
}
```
### 4. Error Handling
Implement retry logic when ad loading fails.
```swift theme={null}
extension GameViewController: AdropRewardedAdDelegate {
func onAdFailedToReceive(_ ad: AdropRewardedAd, _ errorCode: AdropErrorCode) {
print("Ad load failed: \(errorCode)")
// Retry for network errors
if errorCode == .ERROR_CODE_NETWORK {
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { [weak self] in
self?.rewardedAd?.load()
}
}
}
func onAdFailedToShowFullScreen(_ ad: AdropRewardedAd, _ errorCode: AdropErrorCode) {
print("Ad show failed: \(errorCode)")
// Notify user
showAlert(message: "Unable to show ad. Please try again later.")
}
}
```
### 5. Memory Management
Clean up ad object when ViewController is deallocated.
```swift theme={null}
class GameViewController: UIViewController {
private var rewardedAd: AdropRewardedAd?
deinit {
rewardedAd?.delegate = nil
rewardedAd = nil
}
}
```
***
## Testing
### Test Unit ID
Always use a test unit ID during development.
```swift theme={null}
// Development environment
let rewardedAd = AdropRewardedAd(unitId: "PUBLIC_TEST_UNIT_ID_REWARDED")
// Production environment
let rewardedAd = AdropRewardedAd(unitId: "YOUR_PRODUCTION_UNIT_ID")
```
Testing with a real unit ID may be considered invalid traffic and could result in account suspension. Always use a test unit ID.
### Managing Unit IDs by Environment
Automatically switch unit IDs based on build configuration.
```swift theme={null}
enum AdUnitID {
static var rewarded: String {
#if DEBUG
return "PUBLIC_TEST_UNIT_ID_REWARDED"
#else
return "YOUR_PRODUCTION_UNIT_ID"
#endif
}
}
// Usage
let rewardedAd = AdropRewardedAd(unitId: AdUnitID.rewarded)
```
***
## Related Documentation
Interstitial ad implementation guide
AdropErrorCode reference
View complete example code
# Splash Ads
Source: https://docs.adrop.io/sdk/ios/splash
Learn how to implement splash ads in your iOS app.
## Overview
Splash ads are displayed on the splash screen when the app launches. The ad appears alongside your app logo, providing a natural user experience.
### Features
* Natural ad exposure at app launch
* Maintains brand image with app logo
* Fixed size of 360px x 270px
* Supports image ads
Use the test unit ID in development: `PUBLIC_TEST_UNIT_ID_SPLASH`
***
## Caching Behavior
Splash ads are prefetched and cached on the device so the creative can appear instantly at launch, before any network request completes. On the next launch the cached creative is shown first, and a fresh ad request runs in the background to refresh the cache for subsequent launches.
Because of this prefetch model, a creative that is **paused** in Ad Control Console may still be shown from the device cache until the next refresh cycle completes on each user's device. Plan creative pauses and replacements with a buffer for the cache to roll over across the install base.
***
## Ad Size
Splash ads use a fixed size:
* **Size**: 360px x 270px (width x height)
* The ad is displayed at the bottom of the screen, with the app logo placed at the top
***
## Implementation Methods
Splash ads can be implemented in three ways depending on your app's requirements:
1. **Using AdropSplashAdViewController** - The simplest method
2. **Using AdropSplashAdView** - Custom splash screen composition
3. **SwiftUI Implementation** - For SwiftUI apps
***
## Method 1: Using AdropSplashAdViewController
The simplest implementation method. `AdropSplashAdViewController` automatically manages the splash screen.
### UIKit Implementation
```swift theme={null}
import AdropAds
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// Initialize Adrop SDK
Adrop.initialize(production: false)
return true
}
}
```
### Displaying Splash Ad in SceneDelegate
```swift Swift theme={null}
import UIKit
import AdropAds
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(
_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions
) {
guard let windowScene = (scene as? UIWindowScene) else { return }
// Set up window
window = UIWindow(windowScene: windowScene)
// Create splash ad view controller
let splashViewController = AdropSplashAdViewController(
unitId: "YOUR_SPLASH_UNIT_ID"
)
splashViewController.logoImage = UIImage(named: "app_logo") // App logo image
splashViewController.delegate = self
// Set main view controller
splashViewController.mainViewController = MainViewController()
// Set display duration (deprecated — set in console instead, default is 1 second)
splashViewController.displayDuration = 3.0
// Set splash screen as root
window?.rootViewController = splashViewController
window?.makeKeyAndVisible()
}
}
```
```objc Objective-C theme={null}
@import AdropAds;
@interface SceneDelegate ()
@property (nonatomic, strong) UIWindow *window;
@end
@implementation SceneDelegate
- (void)scene:(UIScene *)scene
willConnectToSession:(UISceneSession *)session
options:(UISceneConnectionOptions *)connectionOptions {
UIWindowScene *windowScene = (UIWindowScene *)scene;
if (!windowScene) return;
// Set up window
self.window = [[UIWindow alloc] initWithWindowScene:windowScene];
// Create splash ad view controller
AdropSplashAdViewController *splashViewController =
[[AdropSplashAdViewController alloc] initWithUnitId:@"YOUR_SPLASH_UNIT_ID"];
splashViewController.logoImage = [UIImage imageNamed:@"app_logo"];
splashViewController.delegate = self;
// Set main view controller
splashViewController.mainViewController = [[MainViewController alloc] init];
// Set display duration (deprecated — set in console instead, default is 1 second)
splashViewController.displayDuration = 3.0;
// Set splash screen as root
self.window.rootViewController = splashViewController;
[self.window makeKeyAndVisible];
}
@end
```
### Initialization Parameters
| Parameter | Type | Description |
| - | - | - |
| `unitId` | `String` | Unit ID created in the Ad Control console |
| `adRequestTimeout` | `TimeInterval` | Ad request timeout (seconds). Default is 1.0 |
### Properties
| Property | Type | Description |
| - | - | - |
| `logoImage` | `UIImage?` | App logo image (set as property, not init parameter). Displayed without logo if nil |
| `mainViewController` | `UIViewController?` | Main view controller to transition to after ad closes |
| `backgroundColor` | `UIColor?` | Splash screen background color. Uses systemBackground if nil |
| `displayDuration` | `TimeInterval` | Ad display duration (seconds). Default is 1.0. Deprecated — set in console instead |
### Delegate Implementation
```swift Swift theme={null}
// MARK: - AdropSplashAdDelegate
extension SceneDelegate: AdropSplashAdDelegate {
// Splash ad closed
func onAdClose(_ ad: AdropSplashAd, impressed: Bool) {
print("Splash ad closed - Impressed: \(impressed)")
// Transition to main screen
let mainViewController = MainViewController()
window?.rootViewController = mainViewController
}
// Ad received successfully (optional)
func onAdReceived(_ ad: AdropSplashAd) {
print("Splash ad received successfully")
}
// Ad failed to receive (optional)
func onAdFailedToReceive(_ ad: AdropSplashAd, _ errorCode: AdropErrorCode) {
print("Splash ad failed to receive: \(errorCode)")
// Transitions to main screen even on ad failure
}
// Ad impression (optional)
func onAdImpression(_ ad: AdropSplashAd) {
print("Splash ad impression")
}
}
```
```objc Objective-C theme={null}
#pragma mark - AdropSplashAdDelegate
// Splash ad closed (optional)
- (void)onAdClose:(AdropSplashAd *)ad impressed:(BOOL)impressed {
NSLog(@"Splash ad closed - Impressed: %@", impressed ? @"YES" : @"NO");
// Transition to main screen
MainViewController *mainViewController = [[MainViewController alloc] init];
self.window.rootViewController = mainViewController;
}
// Ad received successfully (optional)
- (void)onAdReceived:(AdropSplashAd *)ad {
NSLog(@"Splash ad received successfully");
}
// Ad failed to receive (optional)
- (void)onAdFailedToReceive:(AdropSplashAd *)ad :(AdropErrorCode)errorCode {
NSLog(@"Splash ad failed to receive: %ld", (long)errorCode);
// Transitions to main screen even on ad failure
}
// Ad impression (optional)
- (void)onAdImpression:(AdropSplashAd *)ad {
NSLog(@"Splash ad impression");
}
```
`AdropSplashAdViewController` automatically handles ad loading, display, and timer management. Even if the ad fails to load, `onAdClose` is automatically called to transition to the main screen.
***
## Integrating with LaunchScreen
The system LaunchScreen is displayed first before showing the splash ad. For a natural user experience, it's recommended to match the style of the LaunchScreen with the splash ad screen.
### LaunchScreen.storyboard Setup
1. Open **LaunchScreen.storyboard** and set the background color to match the splash ad screen
2. Place the app logo in the center (matching the logo position of the splash ad)
3. Verify that `UILaunchStoryboardName` is set to `LaunchScreen` in Info.plist
```xml title="Info.plist" theme={null}
UILaunchStoryboardNameLaunchScreen
```
When the background color and logo position of the LaunchScreen match the splash ad screen, a smooth transition is possible at app launch.
***
## Method 2: Using AdropSplashAdView
Use this when you want to compose a custom splash screen.
```swift theme={null}
import UIKit
import AdropAds
class CustomSplashViewController: UIViewController {
private var splashAdView: AdropSplashAdView?
private let logoImageView = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
// AdropSplashAdView loads automatically on init, so create it first
splashAdView = AdropSplashAdView(unitId: "YOUR_SPLASH_UNIT_ID")
splashAdView?.delegate = self
setupUI()
}
private func setupUI() {
view.backgroundColor = .white
// Set up app logo
logoImageView.image = UIImage(named: "app_logo")
logoImageView.contentMode = .scaleAspectFit
logoImageView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(logoImageView)
// Set up splash ad view
guard let splashAdView = splashAdView else { return }
splashAdView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(splashAdView)
NSLayoutConstraint.activate([
// Logo: center top of screen
logoImageView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
logoImageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 100),
logoImageView.widthAnchor.constraint(equalToConstant: 200),
logoImageView.heightAnchor.constraint(equalToConstant: 200),
// Ad: bottom of screen
splashAdView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
splashAdView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
splashAdView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
splashAdView.heightAnchor.constraint(equalToConstant: 270) // Fixed height
])
}
}
// MARK: - AdropSplashAdViewDelegate
extension CustomSplashViewController: AdropSplashAdViewDelegate {
// Splash ad closed (required)
func onAdClose(_ adView: AdropSplashAdView, impressed: Bool) {
print("Splash ad closed - Impressed: \(impressed)")
transitionToMainScreen(impressed: impressed)
}
// Ad received successfully (optional)
func onAdReceived(_ adView: AdropSplashAdView) {
print("Splash ad view received successfully")
}
// Ad failed to receive (optional)
func onAdFailedToReceive(_ adView: AdropSplashAdView, _ errorCode: AdropErrorCode) {
print("Splash ad view failed to receive: \(errorCode)")
// Transition to main screen on failure
transitionToMainScreen(impressed: false)
}
// Ad impression (optional)
func onAdImpression(_ adView: AdropSplashAdView) {
print("Splash ad view impression")
}
}
// MARK: - Navigation
extension CustomSplashViewController {
private func transitionToMainScreen(impressed: Bool) {
guard let window = view.window else { return }
let mainViewController = MainViewController()
window.rootViewController = mainViewController
// Smooth transition animation
UIView.transition(
with: window,
duration: 0.3,
options: .transitionCrossDissolve,
animations: nil,
completion: nil
)
}
}
```
When using `AdropSplashAdView`, you must implement `AdropSplashAdViewDelegate`. The `onAdClose(impressed:)` method is required.
***
## Method 3: SwiftUI Implementation
How to implement splash ads in a SwiftUI app.
### SwiftUI Wrapper
```swift theme={null}
import SwiftUI
import AdropAds
struct SplashAdView: UIViewControllerRepresentable {
let unitId: String
let logoImage: UIImage?
@Binding var isPresented: Bool
func makeUIViewController(context: Context) -> AdropSplashAdViewController {
let controller = AdropSplashAdViewController(unitId: unitId)
controller.logoImage = logoImage
controller.delegate = context.coordinator
return controller
}
func updateUIViewController(_ uiViewController: AdropSplashAdViewController, context: Context) {
// No update needed
}
func makeCoordinator() -> Coordinator {
Coordinator(isPresented: $isPresented)
}
class Coordinator: NSObject, AdropSplashAdDelegate {
@Binding var isPresented: Bool
init(isPresented: Binding) {
_isPresented = isPresented
}
func onAdClose(_ ad: AdropSplashAd, impressed: Bool) {
print("Splash ad closed - Impressed: \(impressed)")
DispatchQueue.main.async {
self.isPresented = false
}
}
func onAdReceived(_ ad: AdropSplashAd) {
print("Splash ad received successfully")
}
func onAdFailedToReceive(_ ad: AdropSplashAd, _ errorCode: AdropErrorCode) {
print("Splash ad failed to receive: \(errorCode)")
}
func onAdImpression(_ ad: AdropSplashAd) {
print("Splash ad impression")
}
}
}
```
### Using in SwiftUI App
```swift theme={null}
import SwiftUI
import AdropAds
@main
struct MyApp: App {
init() {
// Initialize SDK
Adrop.initialize(production: false)
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
@State private var showSplash = true
var body: some View {
ZStack {
if showSplash {
SplashAdView(
unitId: "YOUR_SPLASH_UNIT_ID",
logoImage: UIImage(named: "app_logo"),
isPresented: $showSplash
)
.ignoresSafeArea()
} else {
MainView()
}
}
}
}
struct MainView: View {
var body: some View {
NavigationView {
VStack {
Text("Main Screen")
.font(.largeTitle)
}
.navigationTitle("Home")
}
}
}
```
***
## Delegate Methods
### AdropSplashAdDelegate
Delegate for using `AdropSplashAd`.
| Method | Required | Description |
| - | - | - |
| `onAdClose(impressed:)` | Optional | Called when splash ad closes. Check ad impression with `impressed` parameter |
| `onAdReceived(_:)` | Optional | Called when ad is received successfully |
| `onAdFailedToReceive(_:_:)` | Optional | Called when ad fails to receive |
| `onAdImpression(_:)` | Optional | Called when ad impression is recorded |
### AdropSplashAdViewDelegate
Delegate for using `AdropSplashAdView`.
| Method | Required | Description |
| - | - | - |
| `onAdClose(impressed:)` | **Required** | Called when splash ad closes. Must handle main screen transition |
| `onAdReceived(_:)` | Optional | Called when ad is received successfully |
| `onAdFailedToReceive(_:_:)` | Optional | Called when ad fails to receive |
| `onAdImpression(_:)` | Optional | Called when ad impression is recorded |
***
## Closure Callbacks
As an alternative to delegates, you can use closure-based callbacks on `AdropSplashAd`.
```swift theme={null}
let splashAd = AdropSplashAd(unitId: "YOUR_UNIT_ID")
splashAd.onAdReceived = { ad in
print("Splash ad received")
}
splashAd.onAdFailedToReceive = { ad, errorCode in
print("Splash ad failed: \(errorCode)")
}
splashAd.onAdImpression = { ad in
print("Splash ad impression")
}
splashAd.onAdClose = { ad, impressed in
print("Splash ad closed - Impressed: \(impressed)")
}
```
If both a delegate and closures are set, both will be called.
***
## The impressed Parameter
The `impressed` parameter in the `onAdClose(_:impressed:)` method indicates whether the ad was actually displayed to the user.
```swift theme={null}
func onAdClose(_ ad: AdropSplashAd, impressed: Bool) {
if impressed {
print("Ad was displayed to the user")
// Post-impression logic
} else {
print("Ad was not displayed (load failure or skipped)")
// No-impression logic
}
// Transition to main screen
transitionToMainScreen()
}
```
### When impressed is false
* Ad load failure
* Network error
* User skipped before seeing the ad
* No ad inventory
***
## Best Practices
### 1. Appropriate Timer Settings
Splash ads should not be displayed too short or too long.
```swift theme={null}
// AdropSplashAdViewController automatically manages the timer
// Default: 1 second (including ad load time)
```
### 2. Optimize Logo Image
Prepare the app logo in an appropriate size to minimize loading time.
```swift theme={null}
// Recommended logo size: 200x200 ~ 300x300 points
let logoImage = UIImage(named: "app_logo") // Prepare @2x, @3x versions
```
### 3. Handle Failures
Ensure the app starts normally even when ad loading fails.
```swift theme={null}
func onAdFailedToReceive(_ ad: AdropSplashAd, _ errorCode: AdropErrorCode) {
print("Ad load failed: \(errorCode)")
// Transition to main screen even on failure
// AdropSplashAdViewController handles this automatically
}
```
### 4. Smooth Screen Transitions
Use natural animations when transitioning to the main screen.
```swift theme={null}
private func transitionToMainScreen() {
guard let window = view.window else { return }
let mainViewController = MainViewController()
UIView.transition(
with: window,
duration: 0.3,
options: .transitionCrossDissolve,
animations: {
window.rootViewController = mainViewController
},
completion: nil
)
}
```
### 5. Separate Test and Production Environments
Test with separate development and production environments.
```swift theme={null}
#if DEBUG
let splashUnitId = "PUBLIC_TEST_UNIT_ID_SPLASH"
let isProduction = false
#else
let splashUnitId = "YOUR_PRODUCTION_SPLASH_UNIT_ID"
let isProduction = true
#endif
// Initialize SDK
Adrop.initialize(production: isProduction)
// Create splash ad
let splashViewController = AdropSplashAdViewController(
unitId: splashUnitId
)
splashViewController.logoImage = UIImage(named: "app_logo")
```
***
## Complete SceneDelegate Example
```swift theme={null}
import UIKit
import AdropAds
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(
_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions
) {
guard let windowScene = (scene as? UIWindowScene) else { return }
// Set up window
window = UIWindow(windowScene: windowScene)
#if DEBUG
let splashUnitId = "PUBLIC_TEST_UNIT_ID_SPLASH"
#else
let splashUnitId = "YOUR_PRODUCTION_SPLASH_UNIT_ID"
#endif
// Create splash ad view controller
let splashViewController = AdropSplashAdViewController(
unitId: splashUnitId
)
splashViewController.logoImage = UIImage(named: "app_logo")
splashViewController.delegate = self
// Set root view controller
window?.rootViewController = splashViewController
window?.makeKeyAndVisible()
}
}
// MARK: - AdropSplashAdDelegate
extension SceneDelegate: AdropSplashAdDelegate {
func onAdClose(_ ad: AdropSplashAd, impressed: Bool) {
print("Splash ad closed - Impressed: \(impressed)")
// Transition to main screen
let mainViewController = MainViewController()
let navigationController = UINavigationController(rootViewController: mainViewController)
guard let window = window else { return }
UIView.transition(
with: window,
duration: 0.3,
options: .transitionCrossDissolve,
animations: {
window.rootViewController = navigationController
},
completion: nil
)
}
func onAdReceived(_ ad: AdropSplashAd) {
print("Splash ad received successfully")
}
func onAdFailedToReceive(_ ad: AdropSplashAd, _ errorCode: AdropErrorCode) {
print("Splash ad failed to receive: \(errorCode)")
// AdropSplashAdViewController automatically calls onAdClose
}
func onAdImpression(_ ad: AdropSplashAd) {
print("Splash ad impression recorded")
}
}
```
***
## Testing
### Test Unit ID
Use the test unit ID during development.
```swift theme={null}
// Test unit ID
let testUnitId = "PUBLIC_TEST_UNIT_ID_SPLASH"
// Or use AdropUnitId
let testUnitId = AdropUnitId.PUBLIC_TEST_UNIT_ID_SPLASH
```
Make sure to use the actual unit ID created in the Ad Control console for production releases. No ad revenue is generated with test unit IDs.
### Debugging
Check ad events with logs.
```swift theme={null}
extension SceneDelegate: AdropSplashAdDelegate {
func onAdReceived(_ ad: AdropSplashAd) {
#if DEBUG
print("Splash ad received")
#endif
}
func onAdFailedToReceive(_ ad: AdropSplashAd, _ errorCode: AdropErrorCode) {
#if DEBUG
print("Splash ad failed: \(errorCode)")
print(" - Check network status")
print(" - Verify unit ID: \(ad.unitId)")
#endif
}
func onAdImpression(_ ad: AdropSplashAd) {
#if DEBUG
print("Splash ad impression")
#endif
}
func onAdClose(_ ad: AdropSplashAd, impressed: Bool) {
#if DEBUG
print("Splash ad closed")
print(" - Impressed: \(impressed)")
#endif
}
}
```
***
## Troubleshooting
* Verify `window.rootViewController` is set correctly
* Check if SceneDelegate is enabled (Info.plist)
* Ensure SDK initialization is complete
* Check network connection
* Verify the unit ID is correct
* Confirm `production: false` setting in test environment
* Check onAdFailedToReceive error code
* Verify `onAdClose(_:impressed:)` delegate is implemented
* Ensure screen transition code runs on main thread
```swift theme={null}
func onAdClose(_ ad: AdropSplashAd, impressed: Bool) {
DispatchQueue.main.async {
// Screen transition code
}
}
```
* Verify `AdropSplashAdViewDelegate` is implemented
* Check `splashAdView.delegate = self` is set
* `onAdClose(impressed:)` method must be implemented
***
## Next Steps
Display interstitial ads during screen transitions
Implement banner ads
Custom design native ads
Provide user rewards
# Targeting Settings
Source: https://docs.adrop.io/sdk/ios/targeting
Learn how to set up audience targeting and contextual targeting.
## Overview
Adrop SDK supports two types of targeting:
* **Audience Targeting**: Target ads based on user attributes (gender, age, interests, etc.)
* **Contextual Targeting**: Target ads based on content or screen context
By utilizing targeting, you can show more relevant ads to users, and advertisers can effectively deliver ads to their desired audience.
***
## Audience Targeting
You can target ads based on user attributes.
### 1. Create Targeting
First, create audience targeting in the Ad Control console.
Learn how to create audience targeting in the console
### 2. Set User ID
You must set the user identifier before rendering ads.
```swift Swift theme={null}
import AdropAds
// Set user ID after login
Adrop.setUID("user_id")
```
```objc Objective-C theme={null}
@import AdropAds;
// Set user ID after login
[Adrop setUID:@"user_id"];
```
Set the UID before entering the ad placement for targeting ads to work correctly.
### 3. Set User Properties
Setting user attributes enables more precise targeting.
```swift Swift theme={null}
import AdropAds
// Set built-in properties
AdropMetrics.setProperty(key: AdropKey.GENDER, value: AdropValue.AdropGender.MALE)
AdropMetrics.setProperty(key: AdropKey.AGE, value: 30)
AdropMetrics.setProperty(key: AdropKey.BIRTH, value: "19931225")
// Set custom properties (must be registered in console)
AdropMetrics.setProperty(key: "membership", value: "premium")
AdropMetrics.setProperty(key: "interest_tech", value: true)
AdropMetrics.setProperty(key: "last_purchase_date", value: "2024-01-15")
```
```objc Objective-C theme={null}
@import AdropAds;
// Set built-in properties
[AdropMetrics setPropertyWithKey:AdropKeyGENDER value:AdropValueAdropGenderMALE];
[AdropMetrics setPropertyWithKey:AdropKeyAGE value:@30];
[AdropMetrics setPropertyWithKey:AdropKeyBIRTH value:@"19931225"];
// Set custom properties (must be registered in console)
[AdropMetrics setPropertyWithKey:@"membership" value:@"premium"];
[AdropMetrics setPropertyWithKey:@"interest_tech" value:@YES];
[AdropMetrics setPropertyWithKey:@"last_purchase_date" value:@"2024-01-15"];
```
The `setProperty()` method may have limited availability in Objective-C due to its `Encodable` parameter type. Use Swift for property configuration when possible.
Property values only support single values (String, Int, Double, Bool). Arrays or dictionaries are not supported.
### Built-in Properties
The SDK provides predefined keys for commonly used attributes.
#### AdropKey
| Key | Description | Value Type |
| - | - | - |
| `AdropKey.GENDER` | Gender | AdropValue.AdropGender |
| `AdropKey.AGE` | Age | Int |
| `AdropKey.BIRTH` | Date of birth | String (YYYY, YYYYMM, YYYYMMDD) |
#### AdropValue
Predefined values for gender:
| Value | Description |
| - | - |
| `AdropValue.AdropGender.MALE` | Male |
| `AdropValue.AdropGender.FEMALE` | Female |
| `AdropValue.AdropGender.OTHER` | Other |
| `AdropValue.UNKNOWN` | Unknown |
```swift Swift Example theme={null}
// Set gender
AdropMetrics.setProperty(key: AdropKey.GENDER, value: AdropValue.AdropGender.MALE)
AdropMetrics.setProperty(key: AdropKey.GENDER, value: AdropValue.AdropGender.FEMALE)
AdropMetrics.setProperty(key: AdropKey.GENDER, value: AdropValue.AdropGender.OTHER)
// Set age
AdropMetrics.setProperty(key: AdropKey.AGE, value: 25)
// Set date of birth
AdropMetrics.setProperty(key: AdropKey.BIRTH, value: "1999") // Year only
AdropMetrics.setProperty(key: AdropKey.BIRTH, value: "199903") // Year and month
AdropMetrics.setProperty(key: AdropKey.BIRTH, value: "19990315") // Full date
```
```objc Objective-C Example theme={null}
// Set gender
[AdropMetrics setPropertyWithKey:AdropKeyGENDER value:AdropValueAdropGenderMALE];
[AdropMetrics setPropertyWithKey:AdropKeyGENDER value:AdropValueAdropGenderFEMALE];
[AdropMetrics setPropertyWithKey:AdropKeyGENDER value:AdropValueAdropGenderOTHER];
// Set age
[AdropMetrics setPropertyWithKey:AdropKeyAGE value:@25];
// Set date of birth
[AdropMetrics setPropertyWithKey:AdropKeyBIRTH value:@"1999"]; // Year only
[AdropMetrics setPropertyWithKey:AdropKeyBIRTH value:@"199903"]; // Year and month
[AdropMetrics setPropertyWithKey:AdropKeyBIRTH value:@"19990315"]; // Full date
```
### Custom Properties
You can set additional properties registered in the console. All custom property keys are strings, and values support String, Int, Double, and Bool types.
```swift Swift theme={null}
// String values
AdropMetrics.setProperty(key: "membership", value: "premium")
AdropMetrics.setProperty(key: "favorite_category", value: "electronics")
// Integer values
AdropMetrics.setProperty(key: "purchase_count", value: 5)
AdropMetrics.setProperty(key: "loyalty_points", value: 1500)
// Double values
AdropMetrics.setProperty(key: "average_purchase", value: 49.99)
// Boolean values
AdropMetrics.setProperty(key: "is_subscribed", value: true)
AdropMetrics.setProperty(key: "newsletter_opt_in", value: false)
// Remove a property by passing nil
AdropMetrics.setProperty(key: "membership", value: nil)
```
```objc Objective-C theme={null}
// String values
[AdropMetrics setPropertyWithKey:@"membership" value:@"premium"];
[AdropMetrics setPropertyWithKey:@"favorite_category" value:@"electronics"];
// Integer values
[AdropMetrics setPropertyWithKey:@"purchase_count" value:@5];
[AdropMetrics setPropertyWithKey:@"loyalty_points" value:@1500];
// Double values
[AdropMetrics setPropertyWithKey:@"average_purchase" value:@49.99];
// Boolean values
[AdropMetrics setPropertyWithKey:@"is_subscribed" value:@YES];
[AdropMetrics setPropertyWithKey:@"newsletter_opt_in" value:@NO];
// Remove a property by passing nil
[AdropMetrics setPropertyWithKey:@"membership" value:nil];
```
Custom properties must be registered in the console before use.
### Constraints
Note the following constraints when setting properties:
* **Single values only**: Arrays (`[String]`, `[Int]`) or dictionaries (`[String: Any]`) are not supported.
* **Supported types**: Only String, Int, Double, and Bool are available.
* **Key length**: Up to 64 characters.
* **Value length**: String values up to 256 characters.
* **Max properties**: Up to 256 properties per user.
* **Pre-registration required**: Custom properties must be registered in the console first.
```swift theme={null}
// ❌ Wrong usage (arrays/dictionaries)
AdropMetrics.setProperty(key: "interests", value: ["tech", "gaming"]) // Not supported
AdropMetrics.setProperty(key: "profile", value: ["age": 25]) // Not supported
// ✅ Correct usage
AdropMetrics.setProperty(key: "interest_tech", value: true)
AdropMetrics.setProperty(key: "interest_gaming", value: true)
AdropMetrics.setProperty(key: "age", value: 25)
```
***
## Event Tracking
Track user behavior events to build audiences based on user actions. Use `AdropMetrics.sendEvent()` to send events.
`logEvent()` is deprecated. Use `sendEvent()` instead.
### Sending Events
```swift Swift theme={null}
import AdropAds
// Simple event (no parameters)
AdropMetrics.sendEvent(name: "app_open")
// Event with parameters
AdropMetrics.sendEvent(name: "view_item", params: [
"item_id": "SKU-123",
"item_name": "Widget",
"item_category": "Electronics",
"price": 29.99
])
```
```objc Objective-C theme={null}
@import AdropAds;
// Simple event (no parameters)
[AdropMetrics sendEventWithName:@"app_open" params:nil];
// Event with parameters
[AdropMetrics sendEventWithName:@"view_item"
params:@{
@"item_id": @"SKU-123",
@"item_name": @"Widget",
@"item_category": @"Electronics",
@"price": @29.99
}];
```
### Multi-Item Events
For events involving multiple items (e.g., purchase, begin\_checkout), pass an array of dictionaries under the `"items"` key.
```swift Swift theme={null}
import AdropAds
AdropMetrics.sendEvent(name: "purchase", params: [
"tx_id": "tx_123",
"currency": "KRW",
"items": [
["item_id": "A", "item_name": "Product A", "price": 100, "quantity": 1],
["item_id": "B", "item_name": "Product B", "price": 200, "quantity": 2]
]
])
```
```objc Objective-C theme={null}
@import AdropAds;
[AdropMetrics sendEventWithName:@"purchase"
params:@{
@"tx_id": @"tx_123",
@"currency": @"KRW",
@"items": @[
@{@"item_id": @"A", @"item_name": @"Product A", @"price": @100, @"quantity": @1},
@{@"item_id": @"B", @"item_name": @"Product B", @"price": @200, @"quantity": @2}
]
}];
```
### Supported Event Examples
| Event Name | Description | Common Parameters |
| - | - | - |
| `app_open` | App opened | — |
| `sign_up` | User signed up | `method` |
| `view_item` | Viewed item | `item_id`, `item_name`, `price` |
| `add_to_cart` | Added to cart | `item_id`, `item_name`, `price`, `quantity` |
| `add_to_wishlist` | Added to wishlist | `item_id`, `item_name`, `price` |
| `begin_checkout` | Began checkout | `currency`, `items` |
| `purchase` | Purchase completed | `tx_id`, `currency`, `items` |
| `page_view` | Page viewed | `page_id`, `page_category` |
| `click` | Element clicked | `element_id`, `element_type` |
* Event name must be 1–64 characters.
* Parameter key must be 1–64 characters.
* String parameter values are limited to 1024 characters.
* Duplicate events with the same name and parameters are throttled within a 500ms window.
### User Data Consent
You can control whether user data is sent to the server using `userDataConsent`. When set to `false`, property data is stored locally only, and events are not transmitted.
```swift theme={null}
// Check current consent status
let hasConsent = AdropMetrics.userDataConsent
// Set consent status
AdropMetrics.setUserDataConsent(true)
```
When `userDataConsent` is `false`, targeting and event tracking will not function. Ensure consent is set to `true` before using `setProperty()` or `sendEvent()`.
***
## Contextual Targeting
You can target ads based on content or screen context.
### 1. Create Targeting
First, create contextual targeting in the Ad Control console.
Learn how to create contextual targeting in the console
### 2. Set Value on Ad Placement
Pass the Context ID set in the console when loading ads.
#### Banner Ads
```swift Swift theme={null}
import AdropAds
// contextId is set via initializer (read-only property)
let banner = AdropBanner(unitId: "YOUR_UNIT_ID", contextId: "sport")
banner.load()
```
```objc Objective-C theme={null}
@import AdropAds;
// contextId is set via initializer
AdropBanner *banner = [[AdropBanner alloc] initWithUnitId:@"YOUR_UNIT_ID" contextId:@"sport"];
[banner load];
```
#### Native Ads
```swift Swift theme={null}
import AdropAds
class ViewController: UIViewController {
private var nativeAd: AdropNativeAd?
func loadAd() {
// contextId is set via initializer
nativeAd = AdropNativeAd(unitId: "YOUR_UNIT_ID", contextId: "tech")
nativeAd?.delegate = self
nativeAd?.load()
}
}
```
```objc Objective-C theme={null}
@import AdropAds;
@interface ViewController ()
@property (nonatomic, strong) AdropNativeAd *nativeAd;
@end
@implementation ViewController
- (void)loadAd {
// contextId is set via initializer
self.nativeAd = [[AdropNativeAd alloc] initWithUnitId:@"YOUR_UNIT_ID" contextId:@"tech"];
self.nativeAd.delegate = self;
[self.nativeAd load];
}
@end
```
#### Other Ad Formats
Context targeting applies only to banner and native ad requests that include a Context ID (banner and native ads in the Android and iOS SDKs, the Web SDK, and the REST API). Interstitial, rewarded, popup, and splash ads, as well as the Flutter and React Native SDKs, do not send a Context ID. If an ad product that contains these placements uses context targeting, campaigns that include context options are not shown in those placements, and context exclusions are not applied there.
### Dynamic Context Setting
You can set context dynamically based on the screen or content.
```swift theme={null}
import UIKit
import AdropAds
class ArticleViewController: UIViewController {
private var banner: AdropBanner?
var articleCategory: String? // Article category
override func viewDidLoad() {
super.viewDidLoad()
// contextId is set via initializer (read-only property)
let contextId = articleCategory ?? ""
banner = AdropBanner(unitId: "YOUR_UNIT_ID", contextId: contextId)
banner?.delegate = self
view.addSubview(banner!)
banner?.load()
}
}
// Usage example
let sportArticle = ArticleViewController()
sportArticle.articleCategory = "sport"
let techArticle = ArticleViewController()
techArticle.articleCategory = "tech"
```
### Context Setting in SwiftUI
```swift theme={null}
import SwiftUI
import AdropAds
struct ArticleView: View {
let category: String
var body: some View {
VStack {
Text("Article Content")
// Set context dynamically based on category
AdropBannerRepresented(
unitId: "YOUR_UNIT_ID",
contextId: category
)
.frame(width: 320, height: 100)
}
}
}
// Usage example
struct ContentView: View {
var body: some View {
TabView {
ArticleView(category: "sport")
.tabItem { Label("Sports", systemImage: "sportscourt") }
ArticleView(category: "tech")
.tabItem { Label("Tech", systemImage: "cpu") }
ArticleView(category: "business")
.tabItem { Label("Business", systemImage: "chart.line.uptrend.xyaxis") }
}
}
}
```
***
## Full Examples
### Audience Targeting Full Example
```swift theme={null}
import UIKit
import AdropAds
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// Initialize SDK
Adrop.initialize(production: false)
// Set UID after user login
Adrop.setUID("user_12345")
// Set basic user properties
AdropMetrics.setProperty(key: AdropKey.GENDER, value: AdropValue.AdropGender.MALE)
AdropMetrics.setProperty(key: AdropKey.AGE, value: 28)
AdropMetrics.setProperty(key: AdropKey.BIRTH, value: "19960315")
// Set custom properties
AdropMetrics.setProperty(key: "membership", value: "premium")
AdropMetrics.setProperty(key: "interest_tech", value: true)
AdropMetrics.setProperty(key: "interest_sports", value: true)
AdropMetrics.setProperty(key: "purchase_count", value: 15)
// Send app start event
AdropMetrics.sendEvent(name: "app_open")
return true
}
}
class ProductViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Product view event
AdropMetrics.sendEvent(
name: "view_item",
params: [
"item_id": "PROD_123",
"category": "electronics",
"price": 299.99
]
)
}
@IBAction func addToCartTapped(_ sender: UIButton) {
// Add to cart event
AdropMetrics.sendEvent(
name: "add_to_cart",
params: [
"item_id": "PROD_123",
"quantity": 1,
"price": 299.99
]
)
}
func completePurchase() {
// Purchase complete event
AdropMetrics.sendEvent(
name: "purchase",
params: [
"transaction_id": "T67890",
"value": 299.99,
"currency": "USD"
]
)
// Update purchase count
let newPurchaseCount = 16
AdropMetrics.setProperty(key: "purchase_count", value: newPurchaseCount)
}
}
```
### Contextual Targeting Full Example
```swift theme={null}
import UIKit
import AdropAds
// News app example: Display different ads based on article category
class NewsArticleViewController: UIViewController {
private var banner: AdropBanner?
// Article info
var article: Article?
struct Article {
let id: String
let title: String
let category: String // "sport", "tech", "business", "entertainment"
}
override func viewDidLoad() {
super.viewDidLoad()
setupBanner()
loadArticle()
}
private func setupBanner() {
// contextId is read-only — set via initializer
let contextId = article?.category ?? ""
banner = AdropBanner(unitId: "YOUR_UNIT_ID", contextId: contextId)
banner?.delegate = self
if let bannerView = banner {
view.addSubview(bannerView)
bannerView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
bannerView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
bannerView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
bannerView.widthAnchor.constraint(equalToConstant: 320),
bannerView.heightAnchor.constraint(equalToConstant: 100)
])
}
banner?.load()
}
private func loadArticle() {
// Send article view event
if let article = article {
AdropMetrics.sendEvent(
name: "view_article",
params: [
"article_id": article.id,
"category": article.category
]
)
}
}
}
extension NewsArticleViewController: AdropBannerDelegate {
func onAdReceived(_ banner: AdropBanner) {
print("Category '\(article?.category ?? "")' targeted ad loaded successfully")
}
func onAdFailedToReceive(_ banner: AdropBanner, _ errorCode: AdropErrorCode) {
print("Ad load failed: \(errorCode)")
}
func onAdImpression(_ banner: AdropBanner) {
print("Ad impression")
}
func onAdClicked(_ banner: AdropBanner) {
print("Ad clicked")
}
}
// Usage example
let sportArticle = NewsArticleViewController()
sportArticle.article = NewsArticleViewController.Article(
id: "A123",
title: "Premier League Match Results",
category: "sport"
)
let techArticle = NewsArticleViewController()
techArticle.article = NewsArticleViewController.Article(
id: "A124",
title: "New AI Technology Announced",
category: "tech"
)
```
***
## Best Practices
### 1. UID Setting Timing
Set the UID as soon as the user logs in.
```swift theme={null}
// On successful login
func didLoginSuccessfully(userId: String) {
Adrop.setUID(userId)
// Also set user properties
AdropMetrics.setProperty(key: "is_logged_in", value: true)
}
// On logout
func didLogout() {
Adrop.setUID("") // Reset UID
AdropMetrics.setProperty(key: "is_logged_in", value: false)
}
```
### 2. Maintain Property Consistency
Set user properties at app launch or whenever values change.
```swift theme={null}
class UserManager {
func updateUserProfile(profile: UserProfile) {
// Sync properties when profile info updates
AdropMetrics.setProperty(key: AdropKey.AGE, value: profile.age)
AdropMetrics.setProperty(key: "membership", value: profile.membershipLevel)
AdropMetrics.setProperty(key: "interest_\(profile.mainInterest)", value: true)
}
}
```
### 3. Send Meaningful Events
Only send important user actions as events.
```swift theme={null}
// ✅ Good examples: Meaningful actions
AdropMetrics.sendEvent(name: "purchase", params: ["value": 99.99])
AdropMetrics.sendEvent(name: "level_complete", params: ["level": 10])
AdropMetrics.sendEvent(name: "tutorial_complete")
// ❌ Bad examples: Excessive events
AdropMetrics.sendEvent(name: "button_tapped") // Too generic
AdropMetrics.sendEvent(name: "scroll") // Too frequent
```
### 4. Context ID Consistency
Context IDs must exactly match the values registered in the console.
```swift theme={null}
// ✅ Good examples: Clear and consistent IDs (set via initializer)
let sportBanner = AdropBanner(unitId: "YOUR_UNIT_ID", contextId: "sport")
let techBanner = AdropBanner(unitId: "YOUR_UNIT_ID", contextId: "tech")
let bizBanner = AdropBanner(unitId: "YOUR_UNIT_ID", contextId: "business")
// ❌ Bad examples: Inconsistent or ambiguous IDs
let banner1 = AdropBanner(unitId: "YOUR_UNIT_ID", contextId: "Sport") // Case mismatch
let banner2 = AdropBanner(unitId: "YOUR_UNIT_ID", contextId: "technology") // Different from console value
let banner3 = AdropBanner(unitId: "YOUR_UNIT_ID", contextId: "category_1") // Unclear meaning
```
* Do not use commas (,) in Context IDs, because a comma separates multiple values both in the console and in an ad request. When requesting ads, pass the value exactly as entered in the console, without leading or trailing spaces.
* To match more than one context in a single request, pass a comma-separated value such as `hiking,outdoor`. The first 5 IDs are used, and exclusion takes precedence over inclusion. See [Passing Multiple Context IDs](/targeting/context#passing-multiple-context-ids).
* If you don't pass a Context ID, or pass a value that isn't registered in the console, no error occurs. Only campaigns with context targeting are excluded, and other ads can still be served.
***
## Next Steps
Apply targeting to banner ads
Apply targeting to native ads
Create audience targeting in the console
Create contextual targeting in the console
# UMP Integration
Source: https://docs.adrop.io/sdk/ios/ump
Integrate Google UMP for GDPR/CCPA compliance on iOS
## Overview
The User Messaging Platform (UMP) SDK helps you manage user consent for personalized advertising in compliance with GDPR (General Data Protection Regulation) and CCPA (California Consumer Privacy Act).
UMP integration requires the `adrop-ads-backfill` module. Make sure you have completed the [Getting Started](/sdk/ios/overview) installation guide first.
***
## Basic Usage
Use the Consent Manager to request user consent after initializing Adrop.
```swift Swift theme={null}
import AdropAds
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Call after Adrop.initialize()
Adrop.consentManager?.requestConsentInfoUpdate(from: self) { result in
if let error = result.error {
print("Consent error: \(error)")
}
}
}
}
```
```objc Objective-C theme={null}
@import AdropAds;
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Call after Adrop.initialize()
if (Adrop.consentManager != nil) {
[Adrop.consentManager requestConsentInfoUpdateFrom:self completion:^(AdropConsentResult *result) {
if (result.error != nil) {
NSLog(@"Consent error: %@", result.error);
}
}];
}
}
@end
```
***
## Consent Result
The `requestConsentInfoUpdate` callback returns an `AdropConsentResult` object with the following properties:
| Property | Type | Description |
| - | - | - |
| `status` | `AdropConsentStatus` | Current consent status |
| `canRequestAds` | `Bool` | Whether ads can be requested |
| `canShowPersonalizedAds` | `Bool` | Whether personalized ads can be shown |
| `error` | `Error?` | Error if the request failed |
```swift Swift theme={null}
Adrop.consentManager?.requestConsentInfoUpdate(from: self) { result in
print("Status: \(result.status)")
print("Can request ads: \(result.canRequestAds)")
print("Can show personalized ads: \(result.canShowPersonalizedAds)")
if let error = result.error {
print("Error: \(error)")
}
}
```
```objc Objective-C theme={null}
[Adrop.consentManager requestConsentInfoUpdateFrom:self completion:^(AdropConsentResult *result) {
NSLog(@"Status: %ld", (long)result.status);
NSLog(@"Can request ads: %d", result.canRequestAds);
NSLog(@"Can show personalized ads: %d", result.canShowPersonalizedAds);
if (result.error != nil) {
NSLog(@"Error: %@", result.error);
}
}];
```
***
## Consent Status
The consent manager returns one of the following statuses:
| Status | Description |
| - | - |
| `unknown` | Consent status not yet determined |
| `required` | Consent required (popup will be shown) |
| `notRequired` | Consent not required (non-GDPR region) |
| `obtained` | Consent already obtained |
***
## Privacy Options
Users must be able to change their consent after the first prompt. When UMP reports that a privacy options entry point is required, add one to your app — typically a **Privacy settings** item in your settings screen — and open the privacy options form from it.
Requires the iOS SDK (`adrop-ads`) and backfill (`adrop-ads-backfill`) 1.15.0 or later. Keep both on the same version — with an older backfill the status always returns `unknown` and the form is not shown. These two methods are available in Swift only.
### Check whether an entry point is required
```swift theme={null}
import AdropAds
class SettingsViewController: UIViewController {
// Re-check whenever your settings screen appears
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let status = Adrop.consentManager?.getPrivacyOptionsRequirementStatus()
// Show the entry point only when it is required
privacySettingsButton.isHidden = status != .required
}
}
```
| Status | Description |
| - | - |
| `unknown` | Not determined yet — `requestConsentInfoUpdate()` has not completed in this app session, or the installed backfill does not support privacy options |
| `required` | An entry point must be shown |
| `notRequired` | No entry point is needed |
Call `requestConsentInfoUpdate()` on **every app launch**, including for users who already gave consent. The requirement status reflects the current app session only after that call completes — otherwise returning users never see the entry point. The consent form itself is still shown only when it is needed.
### Show the privacy options form
```swift theme={null}
// Open the form from your "Privacy settings" entry point (inside your view controller)
@objc private func privacySettingsTapped() {
Adrop.consentManager?.showPrivacyOptionsForm(from: self) { [weak self] result in
if let error = result.error {
print("Error: \(error)")
return
}
// Consent may have changed — re-check whether the entry point is still required
self?.updatePrivacySettingsVisibility()
}
}
```
When the form closes, the SDK applies the updated consent and then returns an `AdropConsentResult`. The form opens only while an entry point is required; otherwise the stored consent is left unchanged and the result carries the current state with an error.
Pass `nil` instead of a view controller to let UMP present the form on the topmost screen — useful when your settings screen is presented modally.
***
## Debug Settings (Test Mode)
Test GDPR/CCPA consent flows during development:
```swift Swift theme={null}
import AdropAds
#if DEBUG
// Set debug geography before requesting consent
Adrop.consentManager?.setDebugSettings(
testDeviceIdentifiers: ["YOUR_DEVICE_IDENTIFIER"], // Check Xcode console
geography: .EEA // Test GDPR
)
// Reset consent for testing
Adrop.consentManager?.reset()
#endif
```
```objc Objective-C theme={null}
@import AdropAds;
#ifdef DEBUG
// Set debug geography before requesting consent
[Adrop.consentManager setDebugSettingsWithTestDeviceIdentifiers:@[@"YOUR_DEVICE_IDENTIFIER"]
geography:AdropConsentDebugGeographyEEA];
// Reset consent for testing
[Adrop.consentManager reset];
#endif
```
### Debug Geographies
| Geography | Description |
| - | - |
| `disabled` | Use actual device location |
| `EEA` | Test GDPR (European Economic Area) |
| `regulatedUSState` | Test CCPA (California, etc.) |
| `other` | Test non-regulated regions |
Debug settings should only be used during development. Remove or disable them before releasing to production.
`reset()` is for testing only — never call it in production code. To let users change their consent, use `showPrivacyOptionsForm()`.
***
## Additional Methods
| Method | Return Type | Description |
| - | - | - |
| `getConsentStatus()` | `AdropConsentStatus` | Returns the current consent status |
| `canRequestAds()` | `Bool` | Returns whether ads can be requested based on consent |
| `getPrivacyOptionsRequirementStatus()` | `AdropPrivacyOptionsRequirementStatus` | Returns whether a privacy options entry point must be shown |
| `showPrivacyOptionsForm(from:completion:)` | `Void` | Shows the privacy options form so users can change their consent |
| `reset()` | `Void` | Resets consent information (testing only) |
```swift theme={null}
// Check consent status independently
if let status = Adrop.consentManager?.getConsentStatus() {
switch status {
case .obtained:
print("Consent obtained")
case .required:
print("Consent required")
case .notRequired:
print("Consent not required")
case .unknown:
print("Consent status unknown")
}
}
// Check if ads can be requested
if Adrop.consentManager?.canRequestAds() == true {
// Load ads
}
// Reset consent (for testing)
Adrop.consentManager?.reset()
```
***
## Best Practices
Request consent as early as possible in your app lifecycle, ideally right after SDK initialization, and call it on every app launch.
Always handle consent errors gracefully and provide fallback behavior.
Use debug settings to test all consent scenarios (required, not required, obtained) before release.
When the privacy options status is required, show an entry point in your settings screen so users can change their consent at any time.
***
## Related Documentation
Set up user and context targeting
SDK installation and setup guide
Classes, delegates, and error codes
# WebView Guide
Source: https://docs.adrop.io/sdk/ios/webview
How to display backfill ads in WKWebView on iOS
## Overview
Display backfill ads in your WKWebView by registering it with the Adrop SDK. This allows web content within your app to show ads through the Adrop platform.
***
## Info.plist Configuration
Add the following key to your `Info.plist` to enable backfill ads in WebView:
```xml Info.plist theme={null}
GADIntegrationManagerwebview
```
This configuration is required for backfill ads to work properly in WebView. Without it, backfill ads may not display.
***
## Register WebView
```swift Swift theme={null}
import WebKit
import AdropAds
class WebViewController: UIViewController {
@IBOutlet weak var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
// Initialize a WKWebViewConfiguration object.
let webViewConfiguration = WKWebViewConfiguration()
// Let HTML videos with a "playsinline" attribute play inline.
webViewConfiguration.allowsInlineMediaPlayback = true
// Let HTML videos with an "autoplay" attribute play automatically.
webViewConfiguration.mediaTypesRequiringUserActionForPlayback = []
// Initialize the WKWebView with your WKWebViewConfiguration object.
webView = WKWebView(frame: view.frame, configuration: webViewConfiguration)
// Register WebView for backfill ads support
// Call as early as possible, before loading content
Adrop.registerWebView(webView)
// Load your web content
if let url = URL(string: "https://your-website.com") {
webView.load(URLRequest(url: url))
}
}
}
```
```objc Objective-C theme={null}
@import WebKit;
@import AdropAds;
@interface WebViewController ()
@property (nonatomic, weak) IBOutlet WKWebView *webView;
@end
@implementation WebViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Initialize a WKWebViewConfiguration object.
WKWebViewConfiguration *webViewConfiguration = [[WKWebViewConfiguration alloc] init];
// Let HTML videos with a "playsinline" attribute play inline.
webViewConfiguration.allowsInlineMediaPlayback = YES;
// Let HTML videos with an "autoplay" attribute play automatically.
webViewConfiguration.mediaTypesRequiringUserActionForPlayback = WKAudiovisualMediaTypeNone;
// Initialize the WKWebView with your WKWebViewConfiguration object.
self.webView = [[WKWebView alloc] initWithFrame:self.view.frame configuration:webViewConfiguration];
[self.view addSubview:self.webView];
// Register WebView for backfill ads support
// Call as early as possible, before loading content
[Adrop registerWebView:self.webView];
// Load your web content
NSURL *url = [NSURL URLWithString:@"https://your-website.com"];
[self.webView loadRequest:[NSURLRequest requestWithURL:url]];
}
@end
```
`registerWebView()` must be called on the main thread.
Call this method as early as possible (e.g., in `viewDidLoad`), before loading any web content.
Backfill ads in WebView requires `adrop-ads-backfill` module. If the module is not installed, `registerWebView()` is silently ignored.
***
## WebView Settings (For Ad Optimization)
Google states that the following WKWebView settings are required for proper ad functionality. WKWebView's iOS defaults are generally suitable, but explicitly verify the items below.
| Setting | Value | Reason |
| - | - | - |
| `allowsInlineMediaPlayback` | `true` | Play HTML videos with `playsinline` attribute inline |
| `mediaTypesRequiringUserActionForPlayback` | `[]` | Play HTML videos with `autoplay` attribute automatically |
| JavaScript | Enabled (keep default) | Required for ad script execution |
| Third-party cookies (`WKWebsiteDataStore`) | Keep default | Frequency capping and personalization signals |
| Content loading | Use `load(_:)` with a network URL | Google guidance: cookies and page URL context matter for monetization. Avoid `loadHTMLString`. |
Ads may not function correctly if you customize `WKWebsiteDataStore` or explicitly disable JavaScript.
***
## Handling External URLs
If your app opens non-own-domain URLs in an external browser, you must ensure that ad resource requests (e.g., `googleads.g.doubleclick.net`) are not blocked.
Ad resources such as iframes and scripts are loaded automatically by the ad SDK — they are not user-initiated navigations. Only redirect **user-initiated main frame navigations** to an external browser.
```swift Swift theme={null}
import UIKit
import WebKit
let allowedHost = "my-domain.com"
func webView(_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
guard let url = navigationAction.request.url else {
decisionHandler(.cancel)
return
}
let host = url.host ?? ""
// Only redirect user-initiated navigations to external browser
// Ad resources (e.g., googleads.g.doubleclick.net) load via iframes/scripts,
// not user clicks — do not block them
if !host.contains(allowedHost),
navigationAction.navigationType == .linkActivated {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
decisionHandler(.cancel)
return
}
decisionHandler(.allow)
}
```
```objc Objective-C theme={null}
NSString *allowedHost = @"my-domain.com";
- (void)webView:(WKWebView *)webView
decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
NSURL *url = navigationAction.request.URL;
if (!url) {
decisionHandler(WKNavigationActionPolicyCancel);
return;
}
NSString *host = url.host ?: @"";
// Only redirect user-initiated navigations to external browser
// Ad resources (e.g., googleads.g.doubleclick.net) load via iframes/scripts,
// not user clicks — do not block them
if (![host containsString:allowedHost] &&
navigationAction.navigationType == WKNavigationTypeLinkActivated) {
[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
decisionHandler(WKNavigationActionPolicyCancel);
return;
}
decisionHandler(WKNavigationActionPolicyAllow);
}
```
Do not block or redirect all non-own-domain requests. Ad resources like `googleads.g.doubleclick.net` are loaded automatically during the backfill ad process and must be allowed to load within the WebView.
***
## Related Documentation
Learn how to integrate banner ads
API reference
Example repository
# Prerequisites
Source: https://docs.adrop.io/sdk/overview
Prerequisites for Ad Control SDK integration.
## Prerequisites
The following information is required for SDK integration:
| Item | Description | Location |
| - | - | - |
| **App ID** | Used for SDK initialization | Settings > App |
| **Ad Unit ID** | Used for ad requests | Ad Unit menu |
You can register your app during the [ad unit creation](/ad-unit/create) process.
If you don't have an ad platform yet, ask your console administrator to [create a platform](/ad-platform/create).
***
## Test Environment
Setting up a test environment allows you to receive test ads even without live ad campaigns. Use the test keys and unit IDs below.
### Test App Keys
`adrop_service.json` file content:
```json theme={null}
{"app_key":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcHBJZCI6IjAxSEQ1UTNaOEdYTk5XRjQ2NENOV0NNQlM0OjAxSEQ1UTlER0IzUk40VlBUVlk5U0hYOTFCIiwia2V5VHlwZSI6MSwiaWF0IjoxNjk3Nzc4MDIxLCJleHAiOjI1MzQwMjEyODAwMH0.uPHxYjFNEJVMshF7mv9g5gUDRcaBOXaHviVYMhyggtc"}
```
`adrop_service.json` file content:
```json theme={null}
{"app_key":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcHBJZCI6IjAxSEQ1UTNaOEdYTk5XRjQ2NENOV0NNQlM0OjAxSEQ1UUJIWFNWNTk5U000MUU1VFlHQU1TIiwia2V5VHlwZSI6MSwiaWF0IjoxNjk3Nzc4MDkxLCJleHAiOjI1MzQwMjEyODAwMH0.5Lut-0y71-IB3fEo1srnX9EIc622kBat4nRRnsp5vsk"}
```
Authorization header value:
```
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcHBJZCI6IjAxSEQ1UTNaOEdYTk5XRjQ2NENOV0NNQlM0OjAxSEZCTU1HRkZXUU5OMk5aVkpCNU5GUFZaIiwia2V5VHlwZSI6MSwiaWF0IjoxNzAwMTI0MDQ5LCJleHAiOjI1MzQwMjEyODAwMH0.J877rLr4xvUWqcEzsesEp_KNzO8XnHaJeESPXdABubQ
```
Use the correct key for your platform. Android and iOS require different App Keys.
### Test Ad Unit IDs
| Format | Unit ID |
| - | - |
| Banner 320x50 | `PUBLIC_TEST_UNIT_ID_320_50` |
| Banner 320x100 | `PUBLIC_TEST_UNIT_ID_320_100` |
| Banner 300x250 | `PUBLIC_TEST_UNIT_ID_300_250` |
| Carousel Banner | `PUBLIC_TEST_UNIT_ID_CAROUSEL` |
| Interstitial | `PUBLIC_TEST_UNIT_ID_INTERSTITIAL` |
| Rewarded | `PUBLIC_TEST_UNIT_ID_REWARDED` |
| Native | `PUBLIC_TEST_UNIT_ID_NATIVE` |
| Popup (Bottom) | `PUBLIC_TEST_UNIT_ID_POPUP_BOTTOM` |
| Popup (Center) | `PUBLIC_TEST_UNIT_ID_POPUP_CENTER` |
| Splash | `PUBLIC_TEST_UNIT_ID_SPLASH` |
### Video Ad Unit IDs
| Format | Unit ID |
| - | - |
| Banner Video 16:9 | `PUBLIC_TEST_UNIT_ID_BANNER_VIDEO_16_9` |
| Banner Video 9:16 | `PUBLIC_TEST_UNIT_ID_BANNER_VIDEO_9_16` |
| Native Video 16:9 | `PUBLIC_TEST_UNIT_ID_NATIVE_VIDEO_16_9` |
| Native Video 9:16 | `PUBLIC_TEST_UNIT_ID_NATIVE_VIDEO_9_16` |
| Popup Video (Bottom 16:9) | `PUBLIC_TEST_UNIT_ID_POPUP_BOTTOM_VIDEO_16_9` |
| Popup Video (Bottom 9:16) | `PUBLIC_TEST_UNIT_ID_POPUP_BOTTOM_VIDEO_9_16` |
| Popup Video (Center 16:9) | `PUBLIC_TEST_UNIT_ID_POPUP_CENTER_VIDEO_16_9` |
| Popup Video (Center 9:16) | `PUBLIC_TEST_UNIT_ID_POPUP_CENTER_VIDEO_9_16` |
***
## Supported Platforms
Android SDK integration guide
iOS SDK integration guide
React Native SDK integration guide
Flutter SDK integration guide
Web SDK integration guide (NPM, CDN, including mobile WebView)
## Supported Ad Formats
| Format | Android / iOS | Flutter / RN | Web |
| - | :-: | :-: | :-: |
| Banner | O | O | O |
| Native | O | O | O |
| Interstitial | O | O | - |
| Rewarded | O | O | - |
| Popup | O | O | - |
| Splash | O | O | - |
Backfill ads support Banner, Native, Interstitial, and Rewarded formats. (Web supports only Banner and Native)
## SDK Data Collection
The following information is automatically collected when the SDK is installed:
* Platform and app information (app name, version)
* OS information (version, type)
* Device information (screen size)
* Locale and timezone
Advertising IDs (ADID/IDFA) are only collected after user consent (e.g., ATT) or when provided directly by the client.
# Banner Ads
Source: https://docs.adrop.io/sdk/react-native/banner
Guide to implementing banner ads in React Native apps.
## Overview
Banner ads are rectangular ads displayed in a portion of the screen. They can be easily implemented using the `AdropBanner` component.
### Key Features
* Can be fixed at the top, bottom, or middle of the screen
* Support for image and video ads
* Support for automatic or manual ad loading
* Ad event handling through callbacks
* Provides ad metadata (creative ID, campaign ID, etc.)
Use test unit IDs in development: `PUBLIC_TEST_UNIT_ID_320_100` or `PUBLIC_TEST_UNIT_ID_320_50`
***
## AdropBanner Component
### Props
Unit ID created in the Ad Control Console
Banner style (width and height required)
Whether to automatically load ads when the component is mounted
Whether to use custom click handling. If set to `true`, the browser won't automatically open on ad click.
Explicit ad size to request from the server. When `style.width` is a number, `adSize` is automatically derived from the style dimensions (`{ width: style.width, height: style.height }`). When `style.width` is a string (e.g., `'100%'`), `adSize` defaults to `null` and the SDK uses responsive sizing. You generally do not need to set this prop manually.
Callback invoked when ad is successfully received
Callback invoked when user clicks the ad
Callback invoked when ad is displayed on screen
Callback invoked when ad reception fails
Callback invoked when a video ad starts playing
Callback invoked when a video ad finishes playing
***
## Basic Usage
### Auto Load (Recommended)
Automatically load the ad when the component is mounted.
```typescript theme={null}
import React from 'react'
import { View, StyleSheet, Dimensions } from 'react-native'
import { AdropBanner } from 'adrop-ads-react-native'
const App = () => {
const screenWidth = Dimensions.get('window').width
return (
{/* Main content */}
{/* ... */}
{/* Banner ad */}
{
console.log('Banner ad received successfully', unitId, metadata)
}}
onAdFailedToReceive={(unitId, errorCode) => {
console.log('Banner ad failed to receive', unitId, errorCode)
}}
/>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
content: {
flex: 1,
},
})
export default App
```
***
## Manual Load Using Ref
You can load ads at the desired time using `ref`.
```typescript theme={null}
import React, { useRef } from 'react'
import { View, Button, StyleSheet, Dimensions } from 'react-native'
import { AdropBanner } from 'adrop-ads-react-native'
interface AdropBannerRef {
load: () => void
}
const App = () => {
const bannerRef = useRef(null)
const screenWidth = Dimensions.get('window').width
const loadBanner = () => {
bannerRef.current?.load()
}
return (
{
console.log('Banner ad received successfully', unitId, metadata)
}}
onAdFailedToReceive={(unitId, errorCode) => {
console.log('Banner ad failed to receive', unitId, errorCode)
}}
/>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
})
export default App
```
***
## Callback Functions
### onAdReceived
Called when ad reception is successful.
```typescript theme={null}
const handleAdReceived = (unitId: string, metadata?: AdropBannerMetadata) => {
console.log('Banner ad received successfully')
console.log('Unit ID:', unitId)
console.log('Creative ID:', metadata?.creativeId)
console.log('Campaign ID:', metadata?.campaignId)
console.log('Transaction ID:', metadata?.txId)
console.log('Destination URL:', metadata?.destinationURL)
// Handle actions such as hiding loading indicator
}
```
### onAdClicked
Called when user clicks the ad.
```typescript theme={null}
const handleAdClicked = (unitId: string, metadata?: AdropBannerMetadata) => {
console.log('Banner ad clicked')
console.log('Unit ID:', unitId)
console.log('Destination URL:', metadata?.destinationURL)
// Handle actions such as click analytics logging
}
```
### onAdImpression
Called when ad is displayed on screen.
```typescript theme={null}
const handleAdImpression = (unitId: string, metadata?: AdropBannerMetadata) => {
console.log('Banner ad impression')
console.log('Unit ID:', unitId)
console.log('Creative ID:', metadata?.creativeId)
// Handle actions such as impression analytics logging
}
```
### onAdFailedToReceive
Called when ad reception fails.
```typescript theme={null}
const handleAdFailedToReceive = (unitId: string, errorCode?: string) => {
console.log('Banner ad failed to receive')
console.log('Unit ID:', unitId)
console.log('Error code:', errorCode)
// Handle errors and display fallback content
if (errorCode === AdropErrorCode.network) {
console.log('Please check your network connection')
} else if (errorCode === AdropErrorCode.adNoFill) {
console.log('No ads available to display')
}
}
```
### onAdVideoStart
Called when a video ad starts playing.
```typescript theme={null}
const handleAdVideoStart = (unitId: string) => {
console.log('Video ad started playing')
console.log('Unit ID:', unitId)
}
```
### onAdVideoEnd
Called when a video ad finishes playing.
```typescript theme={null}
const handleAdVideoEnd = (unitId: string) => {
console.log('Video ad finished playing')
console.log('Unit ID:', unitId)
}
```
***
## AdropBannerMetadata
Type for ad metadata.
```typescript theme={null}
type AdropBannerMetadata = {
creativeId: string // Creative ID
txId: string // Transaction ID
campaignId: string // Campaign ID
destinationURL: string // Destination URL
browserTarget: BrowserTarget // Browser target (EXTERNAL = 0, INTERNAL = 1)
}
```
This information can be useful for ad analytics, tracking, and reporting.
***
## Ad Sizes
Banner ads should have style dimensions that match the size set in the unit.
### Common Banner Sizes
| Size | Usage |
| - | - |
| 320 x 50 | Small banner |
| 320 x 100 | Medium banner |
| 16:9 ratio | Video banner |
### Using Fixed Size
```typescript theme={null}
```
### Fitting to Screen Width
```typescript theme={null}
import { Dimensions } from 'react-native'
const screenWidth = Dimensions.get('window').width
```
### Using Responsive Size
```typescript theme={null}
```
If `width` is set as a string (e.g., `'100%'`), it works responsively, but if set as a number, that value is passed to the ad server to request ads of appropriate size.
***
## Test Unit IDs
Use the following test unit IDs during development and testing.
| Ad Type | Test Unit ID | Size |
| - | - | - |
| Banner (320x50) | `PUBLIC_TEST_UNIT_ID_320_50` | 320 x 50 |
| Banner (320x100) | `PUBLIC_TEST_UNIT_ID_320_100` | 320 x 100 |
### Usage Example
```typescript theme={null}
```
***
## Error Handling
Implement appropriate error handling when ad loading fails.
### Managing Error State
```typescript theme={null}
import React, { useState } from 'react'
import { View, Text, StyleSheet } from 'react-native'
import { AdropBanner } from 'adrop-ads-react-native'
const App = () => {
const [error, setError] = useState(null)
const handleAdReceived = () => {
setError(null)
}
const handleAdFailedToReceive = (unitId: string, errorCode?: string) => {
setError(errorCode || 'Unknown error')
}
return (
{error && (
Ad loading failed: {error}
)}
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
errorText: {
marginTop: 8,
color: 'red',
},
})
export default App
```
***
## Best Practices
### 1. Screen Visibility
Load ads when the banner is visible on screen.
```typescript theme={null}
import React, { useEffect, useRef } from 'react'
import { AppState } from 'react-native'
const App = () => {
const bannerRef = useRef<{ load: () => void }>(null)
const appState = useRef(AppState.currentState)
useEffect(() => {
const subscription = AppState.addEventListener('change', nextAppState => {
if (
appState.current.match(/inactive|background/) &&
nextAppState === 'active'
) {
// Refresh ad when app returns to foreground
bannerRef.current?.load()
}
appState.current = nextAppState
})
return () => {
subscription.remove()
}
}, [])
return (
)
}
```
### 2. Loading Indicator
Improve user experience during ad loading.
```typescript theme={null}
import React, { useState } from 'react'
import { View, ActivityIndicator, StyleSheet } from 'react-native'
import { AdropBanner } from 'adrop-ads-react-native'
const App = () => {
const [loading, setLoading] = useState(true)
return (
{loading && (
)}
setLoading(false)}
onAdFailedToReceive={() => setLoading(false)}
/>
)
}
const styles = StyleSheet.create({
container: {
position: 'relative',
},
loader: {
position: 'absolute',
top: '50%',
left: '50%',
transform: [{ translateX: -12 }, { translateY: -12 }],
},
})
```
### 3. Managing Multiple Banners
Manage the state of each banner when using multiple banners.
```typescript theme={null}
import React, { useState } from 'react'
import { View, Text } from 'react-native'
import { AdropBanner } from 'adrop-ads-react-native'
const App = () => {
const [banner1Loaded, setBanner1Loaded] = useState(false)
const [banner2Loaded, setBanner2Loaded] = useState(false)
return (
Banner 1: {banner1Loaded ? 'Loaded' : 'Loading'} setBanner1Loaded(true)}
/>
Banner 2: {banner2Loaded ? 'Loaded' : 'Loading'} setBanner2Loaded(true)}
/>
)
}
```
### 4. Custom Click Handling
Use `useCustomClick` to handle ad clicks directly.
```typescript theme={null}
import { Linking } from 'react-native'
import { AdropBanner, type AdropBannerMetadata } from 'adrop-ads-react-native'
const App = () => {
const handleAdClicked = (unitId: string, metadata?: AdropBannerMetadata) => {
console.log('Ad clicked:', unitId)
// Custom analytics logging
analytics.logEvent('ad_clicked', {
unitId,
campaignId: metadata?.campaignId,
})
// Manually open browser
if (metadata?.destinationURL) {
Linking.openURL(metadata.destinationURL)
}
}
return (
)
}
```
***
## Complete Example
### TypeScript Example
```typescript theme={null}
import React, { useRef, useState } from 'react'
import {
View,
Text,
Button,
StyleSheet,
Dimensions,
ActivityIndicator,
} from 'react-native'
import { AdropBanner, type AdropBannerMetadata } from 'adrop-ads-react-native'
interface AdropBannerRef {
load: () => void
}
const BannerExample = () => {
const bannerRef = useRef(null)
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
const [metadata, setMetadata] = useState(null)
const screenWidth = Dimensions.get('window').width
const loadBanner = () => {
setLoading(true)
setError(null)
bannerRef.current?.load()
}
const handleAdReceived = (unitId: string, metadata?: AdropBannerMetadata) => {
console.log('✅ Banner ad received successfully')
console.log('Unit ID:', unitId)
console.log('Metadata:', metadata)
setLoading(false)
setError(null)
if (metadata) {
setMetadata(metadata)
}
}
const handleAdClicked = (unitId: string, metadata?: AdropBannerMetadata) => {
console.log('👆 Banner ad clicked')
console.log('Unit ID:', unitId)
console.log('Destination URL:', metadata?.destinationURL)
}
const handleAdImpression = (unitId: string, metadata?: AdropBannerMetadata) => {
console.log('👁️ Banner ad impression')
console.log('Unit ID:', unitId)
console.log('Creative ID:', metadata?.creativeId)
}
const handleAdFailedToReceive = (unitId: string, errorCode?: string) => {
console.log('❌ Banner ad failed to receive')
console.log('Unit ID:', unitId)
console.log('Error code:', errorCode)
setLoading(false)
setError(errorCode || 'Unknown error')
}
return (
Banner Ad Example
{loading && (
)}
{error && (
Error: {error}
)}
{metadata && (
Ad Metadata:
Creative ID: {metadata.creativeId}
Campaign ID: {metadata.campaignId}
Transaction ID: {metadata.txId}
)}
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
marginVertical: 50,
paddingHorizontal: 16,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 20,
},
bannerContainer: {
marginTop: 20,
position: 'relative',
width: '100%',
},
loader: {
position: 'absolute',
top: 40,
left: '50%',
transform: [{ translateX: -12 }],
zIndex: 1,
},
errorContainer: {
marginTop: 8,
padding: 8,
backgroundColor: '#ffebee',
borderRadius: 4,
},
errorText: {
color: '#c62828',
textAlign: 'center',
},
metadataContainer: {
marginTop: 12,
padding: 12,
backgroundColor: '#f5f5f5',
borderRadius: 4,
},
metadataTitle: {
fontSize: 14,
fontWeight: 'bold',
marginBottom: 4,
},
metadataText: {
fontSize: 12,
color: '#666',
marginTop: 2,
},
})
export default BannerExample
```
### JavaScript Example
```javascript theme={null}
import React, { useRef, useState } from 'react'
import {
View,
Text,
Button,
StyleSheet,
Dimensions,
ActivityIndicator,
} from 'react-native'
import { AdropBanner } from 'adrop-ads-react-native'
const BannerExample = () => {
const bannerRef = useRef(null)
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
const screenWidth = Dimensions.get('window').width
const loadBanner = () => {
setLoading(true)
setError(null)
bannerRef.current?.load()
}
const handleAdReceived = (unitId, metadata) => {
console.log('Banner ad received successfully', unitId, metadata)
setLoading(false)
setError(null)
}
const handleAdFailedToReceive = (unitId, errorCode) => {
console.log('Banner ad failed to receive', unitId, errorCode)
setLoading(false)
setError(errorCode || 'Unknown error')
}
return (
Banner Ad Example
{loading && }
{error && (
Error: {error}
)}
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
marginVertical: 50,
paddingHorizontal: 16,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 20,
},
bannerContainer: {
marginTop: 20,
width: '100%',
},
loader: {
marginVertical: 20,
},
errorText: {
marginTop: 8,
color: 'red',
textAlign: 'center',
},
})
export default BannerExample
```
***
## Backfill Ads
To use backfill ads, you must have the backfill dependency added to your native platforms (Android/iOS).
When backfill ads are enabled, the SDK automatically falls back to backfill ads when no direct ad is available. You can handle backfill-specific error codes in the `onAdFailedToReceive` callback.
```tsx theme={null}
{
console.log('Ad loaded:', metadata?.creativeId)
}}
onAdFailedToReceive={(unitId, errorCode) => {
switch (errorCode) {
case AdropErrorCode.adNoFill:
console.log('No direct ad available, requesting backfill...')
break
case AdropErrorCode.backfillNoFill:
console.log('No backfill ad available either')
break
default:
console.log('Ad load failed:', errorCode)
}
}}
/>
```
***
## Next Steps
Implement customizable native ads for your UI
Implement full-screen interstitial ads
Implement ads displayed in popup format
Reference types, methods, and error codes
# Examples
Source: https://docs.adrop.io/sdk/react-native/examples
Provides various usage examples for the React Native SDK.
## Overview
This document provides practical examples of the Adrop React Native SDK. All examples are written in TypeScript and can be used directly in actual projects.
***
## Sample Project
View the complete sample project on GitHub.
React Native sample app written in TypeScript
***
## Banner Ad Example
A complete example of manually loading banner ads using refs and handling errors.
```typescript BannerExample.tsx theme={null}
import React, { useMemo, useRef, useState } from 'react'
import {
Button,
Dimensions,
Platform,
StyleSheet,
Text,
View,
} from 'react-native'
import { AdropBanner, type AdropBannerMetadata } from 'adrop-ads-react-native'
interface IBanner {
load: () => void
}
const BannerExample: React.FC = () => {
const bannerRef = useRef(null)
const emptyBannerRef = useRef(null)
const [errorCode, setErrorCode] = useState('')
const [emptyErrorCode, setEmptyErrorCode] = useState('')
const unit = useMemo(() => {
// Use your actual banner ad unit IDs here
return Platform.OS === 'android'
? 'PUBLIC_TEST_UNIT_ID_320_50'
: 'PUBLIC_TEST_UNIT_ID_320_50'
}, [])
const loadBanner = () => {
bannerRef.current?.load()
setErrorCode('')
}
const loadEmptyBanner = () => {
emptyBannerRef.current?.load()
setEmptyErrorCode('')
}
const onAdClicked = (unitId: string, metadata?: AdropBannerMetadata) =>
console.log('banner clicked', unitId, metadata)
const onAdReceived = (unitId: string, metadata?: AdropBannerMetadata) =>
console.log('banner received', unitId, metadata)
const onAdFailedToReceive = (unitId: string, error?: string) => {
console.log('banner onAdFailedToReceive', unitId, error)
setErrorCode(error ?? '')
}
const onAdImpression = (unitId: string, metadata?: AdropBannerMetadata) =>
console.log('banner onAdImpression', unitId, metadata)
const onEmptyAdFailedToReceive = (_: string, error?: string) => {
console.log('banner onAdFailedToReceive', _, error)
setEmptyErrorCode(error ?? '')
}
const screenWidth = Dimensions.get('window').width
return (
{errorCode && (
Error Code: {errorCode}
)}
{emptyErrorCode && (
Error Code: {emptyErrorCode}
)}
)
}
export default BannerExample
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
marginVertical: 50,
paddingHorizontal: 16,
},
banner: {
width: '100%',
height: 80,
marginVertical: 4,
},
error: {
marginVertical: 2,
color: 'black',
textAlign: 'center',
},
divider: {
width: '100%',
height: 1,
marginVertical: 16,
backgroundColor: 'black',
},
})
```
### Key Features
* **Manual Load**: Set `autoLoad={false}` and load ads at desired timing via ref
* **Error Handling**: Manage error state with `onAdFailedToReceive` callback
* **Multiple Banners**: Manage multiple banners independently
* **Platform Support**: Set platform-specific unit IDs using `Platform.OS`
***
## Native Ad Example
An example of using native ads with WebView and handling backfill ads.
```typescript NativeAdExample.tsx theme={null}
import React, { useCallback, useEffect, useMemo, useState } from 'react'
import {
AdropBodyView,
AdropHeadLineView,
AdropMediaView,
AdropNativeAd,
AdropNativeAdView,
AdropProfileLogoView,
AdropProfileNameView,
} from 'adrop-ads-react-native'
import { WebView } from 'react-native-webview'
import {
Button,
Dimensions,
ScrollView,
StyleSheet,
Text,
View,
Linking,
Platform,
} from 'react-native'
import type { AdropNativeAdListener } from 'adrop-ads-react-native'
const NativeAdExample: React.FC = () => {
const [nativeAd, setNativeAd] = useState()
const [isLoaded, setIsLoaded] = useState(false)
const [errorCode, setErrorCode] = useState('')
const disabledReset = !errorCode
const unit = useMemo(() => {
return Platform.OS === 'android'
? 'PUBLIC_TEST_UNIT_ID_NATIVE'
: 'PUBLIC_TEST_UNIT_ID_NATIVE'
}, [])
const openUrl = useCallback((url: string) => {
Linking.openURL(url).catch((err) =>
console.error('Failed to open URL:', err)
)
}, [])
const listener = useMemo(
(): AdropNativeAdListener => ({
onAdReceived: (ad) => {
console.log(
`nativeAd received ${ad.unitId}`,
ad.properties,
ad.txId,
ad.campaignId,
ad.creativeId
)
setIsLoaded(true)
setErrorCode('')
},
onAdFailedToReceive: (_, error) => {
console.log('nativeAd onAdFailedToReceive', error)
setErrorCode(error)
},
onAdClicked: (ad) => console.log(`nativeAd clicked ${ad.unitId}`),
onAdImpression: (ad) =>
console.log(`nativeAd impressed ${ad.unitId}`),
}),
[]
)
const initialize = useCallback(
(unitId: string) => {
let adropNativeAd = new AdropNativeAd(unitId)
adropNativeAd.listener = listener
setNativeAd((prev) => {
prev?.destroy()
return adropNativeAd
})
setIsLoaded(false)
setErrorCode('')
},
[listener]
)
useEffect(() => {
initialize(unit)
}, [initialize, unit])
const load = () => nativeAd?.load()
const resetTestAd = () => {
initialize(unit)
}
const resetEmptyAd = () => {
initialize('TEST_UNIT_ID')
}
const adView = useMemo(() => {
if (!isLoaded) return null
return (
{!nativeAd?.isVideoAd || nativeAd?.isBackfilled ? (
) : (
{
// Android webview event
if (
event.url &&
event.url !== 'about:blank' &&
!event.url.startsWith('data:')
) {
openUrl(event.url)
}
}}
onOpenWindow={(event) => {
// iOS webview event (window.open)
if (event.nativeEvent?.targetUrl) {
openUrl(event.nativeEvent.targetUrl)
}
}}
/>
)}
)
}, [isLoaded, nativeAd, openUrl])
return (
{adView}
After initializing the native ad, click the load button to display the ad.
After initializing the native ad, click the load button to trigger the error callback.
{errorCode && (
Error Code: {errorCode}
)}
)
}
export default NativeAdExample
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
paddingVertical: 5,
paddingHorizontal: 66,
},
button: {
marginVertical: 4,
},
description: {
color: 'black',
marginBottom: 24,
paddingHorizontal: 16,
textAlign: 'center',
},
error: {
color: 'black',
marginVertical: 2,
},
adContainer: {
paddingHorizontal: 16,
},
adStyle: {
width: '100%',
height: 360,
marginBottom: 24,
},
icon: {
width: 32,
height: 32,
marginRight: 8,
},
name: {
fontSize: 14,
fontWeight: 'bold',
color: 'black',
},
headline: {
fontSize: 16,
fontWeight: 'bold',
color: 'black',
},
body: {
fontSize: 14,
color: 'black',
marginVertical: 16,
},
rowContainer: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'flex-start',
alignItems: 'center',
marginBottom: 8,
},
})
```
### Key Features
* **Profile Information Display**: Show advertiser information using `AdropProfileLogoView` and `AdropProfileNameView`
* **WebView Integration**: Render HTML creatives with WebView
* **Backfill Handling**: Handle backfill ads using `isBackfilled` property
* **URL Handling**: Process WebView navigation events to open external links
* **Memory Management**: Clean up ad objects with `destroy()` method
***
## Interstitial Ad Example
An example of implementing interstitial ads using Hooks.
```typescript InterstitialAdExample.tsx theme={null}
import React, { useCallback, useMemo, useState } from 'react'
import { Button, Platform, StyleSheet, Text, View } from 'react-native'
import { useAdropInterstitialAd } from 'adrop-ads-react-native'
const InterstitialAdExample: React.FC = () => {
const unit = useMemo(() => {
return Platform.OS === 'android'
? 'PUBLIC_TEST_UNIT_ID_INTERSTITIAL'
: 'PUBLIC_TEST_UNIT_ID_INTERSTITIAL'
}, [])
const [unitId, setUnitId] = useState(unit)
const { load, show, errorCode, reset, isLoaded, isOpened, isReady } =
useAdropInterstitialAd(unitId)
const disabledReset = !(isOpened || errorCode)
const loadAd = useCallback(() => {
if (isReady) {
load()
}
}, [isReady, load])
const resetTestAd = useCallback(() => {
reset()
setUnitId(unit)
}, [reset, unit])
const resetEmptyAd = useCallback(() => {
reset()
setUnitId('TEST_UNIT_ID')
}, [reset])
return (
After initializing the interstitial ad, click the load button to load the ad.
After initializing the interstitial ad, click the load button to trigger the error callback.
{errorCode && (
Error Code: {errorCode}
)}
)
}
export default InterstitialAdExample
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
marginVertical: 50,
},
button: {
marginVertical: 4,
},
description: {
color: 'black',
marginBottom: 24,
paddingHorizontal: 16,
textAlign: 'center',
},
error: {
color: 'black',
marginVertical: 2,
},
})
```
### Key Features
* **Hook Usage**: Easy state management with `useAdropInterstitialAd` Hook
* **State Tracking**: Track ad state using `isLoaded`, `isOpened`, `isReady` states
* **Error Handling**: Check error state with `errorCode`
* **Initialization**: Initialize ads with `reset()` method
Since interstitial ads cover the entire screen, it's important to display them at appropriate times considering user experience.
***
## Rewarded Ad Example
An example of implementing rewarded ads using Hooks.
```typescript RewardedAdExample.tsx theme={null}
import React, { useCallback, useMemo, useState } from 'react'
import { Button, Platform, StyleSheet, Text, View } from 'react-native'
import { useAdropRewardedAd } from 'adrop-ads-react-native'
const RewardedAdExample: React.FC = () => {
const unit = useMemo(() => {
return Platform.OS === 'android'
? 'PUBLIC_TEST_UNIT_ID_REWARDED'
: 'PUBLIC_TEST_UNIT_ID_REWARDED'
}, [])
const [unitId, setUnitId] = useState(unit)
const { load, show, errorCode, reset, isLoaded, isOpened, isReady } =
useAdropRewardedAd(unitId)
const disabledReset = !(isOpened || errorCode)
const loadAd = useCallback(() => {
if (isReady) {
load()
}
}, [isReady, load])
const resetTestAd = useCallback(() => {
reset()
setUnitId(unit)
}, [reset, unit])
const resetEmptyAd = useCallback(() => {
reset()
setUnitId('TEST_UNIT_ID')
}, [reset])
return (
After initializing the rewarded ad, click the load button to load the ad.
After initializing the rewarded ad, click the load button to trigger the error callback.
{errorCode && (
Error Code: {errorCode}
)}
)
}
export default RewardedAdExample
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
marginVertical: 50,
},
button: {
marginVertical: 4,
},
description: {
color: 'black',
marginBottom: 24,
paddingHorizontal: 16,
textAlign: 'center',
},
error: {
color: 'black',
marginVertical: 2,
},
})
```
### Key Features
* **Hook Usage**: Easy state management with `useAdropRewardedAd` Hook
* **Reward Handling**: Check reward timing via listener (see [Rewarded Ad Guide](/sdk/react-native/rewarded) for listener setup)
* **State Management**: Check ad state with `isLoaded`, `isOpened`, `isReady`
Rewarded ads require users to watch the entire ad to receive rewards. Only grant rewards in the `onAdEarnRewardHandler` callback.
***
## Popup Ad Example
An example of implementing and customizing popup ads using classes.
```typescript PopupAdExample.tsx theme={null}
import React, { useCallback, useEffect, useMemo, useState } from 'react'
import { Button, Platform, StyleSheet, Text, View } from 'react-native'
import {
type AdropListener,
AdropPopupAd,
type AdropPopupAdColors,
} from 'adrop-ads-react-native'
const PopupAdExample: React.FC = () => {
const [popupAd, setPopupAd] = useState()
const [isLoaded, setIsLoaded] = useState(false)
const [isShown, setIsShown] = useState(false)
const [errorCode, setErrorCode] = useState('')
const disabledReset = !(errorCode || isShown)
const unit = useMemo(() => {
return Platform.OS === 'android'
? 'PUBLIC_TEST_UNIT_ID_POPUP_BOTTOM'
: 'PUBLIC_TEST_UNIT_ID_POPUP_BOTTOM'
}, [])
const listener: AdropListener = useMemo(() => {
return {
onAdImpression: (ad: AdropPopupAd) =>
console.log(
`popup impressed: ${ad.unitId}, ${ad.creativeId}, ${
ad.txId
}, ${ad.campaignId} ${ad.destinationURL}`
),
onAdClicked: (ad: AdropPopupAd) => {
console.log(
`popup clicked: ${ad.unitId}, ${ad.destinationURL}`
)
ad.close()
},
onAdReceived: (ad: AdropPopupAd) => {
setIsLoaded(true)
console.log(`popup received: ${ad.unitId}`)
setErrorCode('')
},
onAdFailedToReceive: (_: AdropPopupAd, error: any) => {
console.log('popup onAdFailedToReceive', error)
setErrorCode(error)
},
onAdDidDismissFullScreen: (ad: AdropPopupAd) =>
console.log(`popup dismissed: ${ad.unitId}`),
onAdDidPresentFullScreen: (ad: AdropPopupAd) =>
console.log(`popup presented: ${ad.unitId}`),
onAdFailedToShowFullScreen: (_: AdropPopupAd, error: any) =>
setErrorCode(error),
} as AdropListener
}, [])
useEffect(() => {
return () => {
popupAd?.destroy()
}
}, [popupAd])
const initialize = useCallback(
(unitId: string) => {
// Customize popup ad colors
let hideForTodayTextColor = '#456'
let backgroundColor = 'rgba(53, 255, 63, 0.3)'
let customColors: AdropPopupAdColors = {
hideForTodayTextColor,
backgroundColor,
}
let adropPopupAd = new AdropPopupAd(unitId, customColors)
adropPopupAd.listener = listener
setPopupAd((prev) => {
prev?.destroy()
return adropPopupAd
})
},
[listener]
)
useEffect(() => {
initialize(unit)
}, [initialize, unit])
const load = () => popupAd?.load()
const show = () => {
popupAd?.show()
setIsShown(true)
}
const resetTestAd = () => {
initialize(unit)
resetState()
}
const resetEmptyAd = () => {
initialize('TEST_UNIT_ID')
resetState()
}
const resetState = () => {
setIsLoaded(false)
setIsShown(false)
setErrorCode('')
}
return (
After initializing the popup ad, click the load button to load the ad.
After initializing the popup ad, click the load button to trigger the error callback.
{errorCode && (
Error Code: {errorCode}
)}
)
}
export default PopupAdExample
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
marginVertical: 50,
},
button: {
marginVertical: 4,
},
description: {
color: 'black',
marginBottom: 24,
paddingHorizontal: 16,
textAlign: 'center',
},
error: {
color: 'black',
marginVertical: 2,
},
})
```
### Key Features
* **Class-based**: Direct use of `AdropPopupAd` class
* **Color Customization**: Set popup style with `AdropPopupAdColors`
* **Close on Click**: Call `ad.close()` in `onAdClicked`
* **Memory Management**: Call `destroy()` in `useEffect` cleanup
***
## Targeting Configuration Example
An example of setting ad targeting.
```typescript TargetingExample.tsx theme={null}
import React, { useEffect } from 'react'
import { View } from 'react-native'
import { AdropMetrics, AdropProperties, AdropGender } from 'adrop-ads-react-native'
const TargetingExample: React.FC = () => {
useEffect(() => {
// Set user gender
AdropMetrics.setProperty(AdropProperties.GENDER, AdropGender.MALE)
// Set user age
AdropMetrics.setProperty(AdropProperties.AGE, 25)
// Set user birth date
AdropMetrics.setProperty(AdropProperties.BIRTH, '19980520')
// Set custom property (string)
AdropMetrics.setProperty('customKey', 'customValue')
// Set custom property (number)
AdropMetrics.setProperty('level', 10)
// Set custom property (boolean)
AdropMetrics.setProperty('isPremium', true)
// Send event
AdropMetrics.sendEvent('purchase', {
item_id: 'SKU-001',
price: 29900,
currency: 'KRW',
})
}, [])
return
}
export default TargetingExample
```
### Available Targeting Properties
| Property | Type | Description |
| - | - | - |
| `AdropProperties.GENDER` | `AdropGender` | User gender (`MALE`, `FEMALE`, `OTHER`, `UNKNOWN`) |
| `AdropProperties.AGE` | `number` | User age |
| `AdropProperties.BIRTH` | `string` | User date of birth (yyyyMMdd) |
| Custom key | `string \| number \| boolean` | Custom properties defined by the app |
Targeting properties are sent to the server during ad requests to display more relevant ads.
***
## Error Handling Utility
A utility example that converts error codes into human-readable messages.
```typescript ErrorUtils.ts theme={null}
import { AdropErrorCode } from 'adrop-ads-react-native'
export const descriptionOf = (errorCode: string): string => {
switch (errorCode) {
case AdropErrorCode.network:
return 'Network connection is unstable'
case AdropErrorCode.internal:
return 'An SDK internal error occurred'
case AdropErrorCode.initialize:
return 'Adrop must be initialized first'
case AdropErrorCode.invalidUnit:
return 'Ad unit ID is invalid'
case AdropErrorCode.notTargetCountry:
return 'SDK cannot be used in unsupported countries'
case AdropErrorCode.inactive:
return 'No active ad campaigns available'
case AdropErrorCode.adNoFill:
return 'No ads matching the criteria. Please try again'
case AdropErrorCode.adDuplicated:
return 'Cannot reload after ad has been received'
case AdropErrorCode.adLoading:
return 'Waiting for server response after ad request'
case AdropErrorCode.adEmpty:
return 'No ad received'
case AdropErrorCode.adShown:
return 'This ad has already been shown'
case AdropErrorCode.adHideForToday:
return 'Cannot load more today'
case AdropErrorCode.adLandscapeUnsupported:
return 'Ads cannot be displayed in landscape mode'
case AdropErrorCode.backfillNoFill:
return 'No backfill ad available'
case AdropErrorCode.undefined:
return 'Undefined error'
default:
return ''
}
}
```
```typescript Usage Example theme={null}
import { descriptionOf } from './ErrorUtils'
const handleAdFailedToReceive = (unitId: string, errorCode?: string) => {
if (errorCode) {
const errorMessage = descriptionOf(errorCode)
console.log('Error:', errorMessage)
Alert.alert('Ad Load Failed', errorMessage)
}
}
```
### Error Code List
| Error Code | Description |
| - | - |
| `network` | Network connection issue |
| `internal` | SDK internal error |
| `initialize` | SDK initialization required |
| `invalidUnit` | Invalid unit ID |
| `notTargetCountry` | Unsupported country |
| `inactive` | No active campaigns |
| `adNoFill` | No ads available |
| `adDuplicated` | Duplicate load attempt |
| `adLoading` | Ad loading in progress |
| `adEmpty` | Ad not received |
| `adShown` | Ad already shown |
| `adHideForToday` | "Don't show today" selected |
| `adLandscapeUnsupported` | Landscape mode not supported |
| `backfillNoFill` | No backfill ad available |
***
## Full Integration Example
An example of using all ad formats in a single app.
```typescript App.tsx theme={null}
import React, { useEffect } from 'react'
import { NavigationContainer } from '@react-navigation/native'
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
import { Adrop } from 'adrop-ads-react-native'
import BannerExample from './views/BannerExample'
import NativeAdExample from './views/NativeAdExample'
import InterstitialAdExample from './views/InterstitialAdExample'
import RewardedAdExample from './views/RewardedAdExample'
import PopupAdExample from './views/PopupAdExample'
const Tab = createBottomTabNavigator()
const App = () => {
useEffect(() => {
// Initialize Adrop SDK
Adrop.initialize(false) // test mode: false, production: true
}, [])
return (
)
}
export default App
```
***
## Best Practices
### 1. Memory Management
Always clean up ad objects after use.
```typescript theme={null}
useEffect(() => {
const nativeAd = new AdropNativeAd('YOUR_UNIT_ID')
return () => {
nativeAd.destroy()
}
}, [])
```
### 2. Error Handling
Implement error handling for all ad loads.
```typescript theme={null}
const [error, setError] = useState(null)
const handleAdFailedToReceive = (unitId: string, errorCode?: string) => {
setError(errorCode ?? 'Unknown error')
// Error logging, display fallback content, etc.
}
```
### 3. State Management
Track ad state using Hooks.
```typescript theme={null}
const { isLoaded, isOpened, isReady, errorCode } = useAdropInterstitialAd(unitId)
// Disable button
```
### 4. Platform Branching
Use different unit IDs for each platform.
```typescript theme={null}
const unitId = useMemo(() => {
return Platform.OS === 'android'
? 'ANDROID_UNIT_ID'
: 'IOS_UNIT_ID'
}, [])
```
### 5. Use Test Unit IDs
Always use test unit IDs during development and testing.
```typescript theme={null}
const isProduction = false
const unitId = isProduction
? 'YOUR_PRODUCTION_UNIT_ID'
: 'PUBLIC_TEST_UNIT_ID_320_50'
```
***
## Next Steps
Check the complete API documentation
Detailed banner ad guide
Detailed native ad guide
Detailed interstitial ad guide
# Interstitial Ads
Source: https://docs.adrop.io/sdk/react-native/interstitial
Guide to implementing interstitial ads in React Native apps.
## Overview
Interstitial ads are ads that cover the entire app screen. They are suitable for natural transition points in the app, such as level transitions in games or content page transitions.
### Features
* Immersive ads covering the full screen
* Maintained until user explicitly closes
* Support for image and video ads
* High visual attention
Use test unit ID in development: `PUBLIC_TEST_UNIT_ID_INTERSTITIAL`
***
## Implementation Methods
Adrop React Native SDK provides two ways to implement interstitial ads:
1. **Class Method** - Direct use of AdropInterstitialAd class
2. **Hook Method** - Using useAdropInterstitialAd Hook (Recommended)
***
## AdropInterstitialAd Class
### Constructor
Create an AdropInterstitialAd instance.
```typescript theme={null}
import { AdropInterstitialAd } from 'adrop-ads-react-native'
const interstitialAd = new AdropInterstitialAd(unitId)
```
Ad unit ID (issued from console)
### Properties
Returns whether the ad is loaded.
```typescript theme={null}
if (interstitialAd.isLoaded) {
// Ad can be shown
}
```
Returns the ad unit ID.
```typescript theme={null}
const unitId = interstitialAd.unitId
```
Returns the creative ID of the currently loaded ad.
```typescript theme={null}
const creativeId = interstitialAd.creativeId
```
Returns the transaction ID of the currently loaded ad.
```typescript theme={null}
const txId = interstitialAd.txId
```
Returns the campaign ID of the currently loaded ad.
```typescript theme={null}
const campaignId = interstitialAd.campaignId
```
Returns the destination URL of the currently loaded ad.
```typescript theme={null}
const destinationURL = interstitialAd.destinationURL
```
Returns the browser target setting of the currently loaded ad (`BrowserTarget.EXTERNAL` or `BrowserTarget.INTERNAL`).
```typescript theme={null}
const browserTarget = interstitialAd.browserTarget
```
Sets the listener to receive ad events.
```typescript theme={null}
interstitialAd.listener = {
onAdReceived: (ad) => console.log('Ad received'),
onAdFailedToReceive: (ad, errorCode) => console.log('Reception failed', errorCode),
// ... other callbacks
}
```
### Methods
Requests and loads the ad.
```typescript theme={null}
interstitialAd.load()
```
Shows the loaded ad. Should be called when `isLoaded` is `true`.
```typescript theme={null}
if (interstitialAd.isLoaded) {
interstitialAd.show()
}
```
Closes the currently displayed ad. Useful for handling back button press on Android.
```typescript theme={null}
interstitialAd.close()
```
Destroys the ad instance and releases resources. Must be called on component unmount.
```typescript theme={null}
useEffect(() => {
return () => {
interstitialAd?.destroy()
}
}, [interstitialAd])
```
***
## AdropListener Interface
Callback interface for receiving ad events.
### Recommended Callbacks
Called when ad reception is successful. At this point, you can call `show()` to display the ad.
```typescript theme={null}
onAdReceived: (ad) => {
console.log('Ad received:', ad.unitId)
// Ad ready to show
}
```
Called when ad reception fails. You can check the failure reason through the error code.
```typescript theme={null}
onAdFailedToReceive: (ad, errorCode) => {
console.log('Ad reception failed:', errorCode)
}
```
### Optional Callbacks
Called when ad impression is recorded.
```typescript theme={null}
onAdImpression: (ad) => {
console.log('Ad impression recorded')
}
```
Called when user clicks the ad.
```typescript theme={null}
onAdClicked: (ad) => {
console.log('Ad clicked')
}
```
Called just before the full-screen ad is displayed. You can perform actions such as pausing the game.
```typescript theme={null}
onAdWillPresentFullScreen: (ad) => {
console.log('About to show ad')
// Pause game, stop music, etc.
}
```
Called immediately after the full-screen ad is displayed.
```typescript theme={null}
onAdDidPresentFullScreen: (ad) => {
console.log('Ad displayed')
}
```
Called just before the full-screen ad is closed.
```typescript theme={null}
onAdWillDismissFullScreen: (ad) => {
console.log('About to close ad')
}
```
Called immediately after the full-screen ad is closed. Good time to preload the next ad.
```typescript theme={null}
onAdDidDismissFullScreen: (ad) => {
console.log('Ad closed')
// Resume game, load next ad, etc.
}
```
Called when ad display fails. You can check the failure reason through the error code.
```typescript theme={null}
onAdFailedToShowFullScreen: (ad, errorCode) => {
console.log('Ad display failed:', errorCode)
}
```
Called when the user presses the back button while the ad is displayed. **Android only.**
```typescript theme={null}
onAdBackButtonPressed: (ad) => {
console.log('Back button pressed')
}
```
***
## Class Method Implementation Example
Method using AdropInterstitialAd class directly.
```typescript theme={null}
import React, { useCallback, useEffect, useMemo, useState } from 'react'
import { Button, StyleSheet, Text, View } from 'react-native'
import { AdropInterstitialAd, type AdropListener } from 'adrop-ads-react-native'
const InterstitialAdScreen: React.FC = () => {
const [interstitialAd, setInterstitialAd] = useState()
const [isLoaded, setIsLoaded] = useState(false)
const [errorCode, setErrorCode] = useState('')
// Set up ad event listener
const listener: AdropListener = useMemo(() => ({
onAdReceived: (ad) => {
console.log('Interstitial ad received:', ad.unitId)
setIsLoaded(true)
setErrorCode('')
},
onAdFailedToReceive: (ad, error) => {
console.log('Interstitial ad reception failed:', error)
setErrorCode(error || 'Unknown error')
setIsLoaded(false)
},
onAdClicked: (ad) => {
console.log('Interstitial ad clicked')
},
onAdImpression: (ad) => {
console.log('Interstitial ad impression')
},
onAdWillPresentFullScreen: (ad) => {
console.log('About to show interstitial ad')
// Pause game, stop music, etc.
},
onAdDidPresentFullScreen: (ad) => {
console.log('Interstitial ad displayed')
},
onAdWillDismissFullScreen: (ad) => {
console.log('About to close interstitial ad')
},
onAdDidDismissFullScreen: (ad) => {
console.log('Interstitial ad closed')
setIsLoaded(false)
// Resume game, preload next ad, etc.
},
onAdFailedToShowFullScreen: (ad, error) => {
console.log('Interstitial ad display failed:', error)
setErrorCode(error || 'Unknown error')
setIsLoaded(false)
},
onAdBackButtonPressed: (ad) => {
console.log('Back button pressed')
ad.close() // Close ad on back button press (Android only)
},
}), [])
// Initialize ad instance
useEffect(() => {
const ad = new AdropInterstitialAd('YOUR_UNIT_ID')
ad.listener = listener
setInterstitialAd(ad)
// Clean up on component unmount
return () => {
ad.destroy()
}
}, [listener])
// Load ad
const loadAd = useCallback(() => {
interstitialAd?.load()
}, [interstitialAd])
// Show ad
const showAd = useCallback(() => {
if (interstitialAd?.isLoaded) {
interstitialAd.show()
} else {
console.log('Ad not loaded yet')
}
}, [interstitialAd])
return (
{errorCode && (
Error: {errorCode}
)}
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
gap: 16,
},
error: {
color: 'red',
marginTop: 16,
},
})
export default InterstitialAdScreen
```
***
## useAdropInterstitialAd Hook
You can implement interstitial ads more easily using React Hook.
### Hook Usage
```typescript theme={null}
import { useAdropInterstitialAd } from 'adrop-ads-react-native'
const { load, show, reset, isLoaded, isOpened, isClosed, isClicked, errorCode, isReady } = useAdropInterstitialAd(unitId)
```
Ad unit ID. Passing `null` will release the ad.
### Return Values
Loads the ad. Only works when `isReady` is `true`.
```typescript theme={null}
const handleLoad = () => {
if (isReady) {
load()
}
}
```
Shows the loaded ad.
```typescript theme={null}
const handleShow = () => {
if (isLoaded) {
show()
}
}
```
Resets ad state and creates a new instance.
```typescript theme={null}
const handleReset = () => {
reset()
}
```
Whether the ad instance is ready.
Whether the ad is successfully loaded.
Whether the ad is displayed on screen.
Whether the ad is closed.
Whether the ad is clicked.
Error code occurred during ad loading or display.
Whether the back button was pressed while the ad is displayed. **Android only.**
Browser target setting of the loaded ad (`BrowserTarget.EXTERNAL` or `BrowserTarget.INTERNAL`).
***
## Hook Method Implementation Example
Method using useAdropInterstitialAd Hook (recommended).
```typescript theme={null}
import React, { useCallback, useMemo } from 'react'
import { Button, StyleSheet, Text, View } from 'react-native'
import { useAdropInterstitialAd } from 'adrop-ads-react-native'
const InterstitialAdScreen: React.FC = () => {
const unitId = 'YOUR_UNIT_ID'
const {
load,
show,
reset,
isLoaded,
isOpened,
isClosed,
isClicked,
errorCode,
isReady
} = useAdropInterstitialAd(unitId)
// Load ad
const handleLoad = useCallback(() => {
if (isReady) {
load()
}
}, [isReady, load])
// Show ad
const handleShow = useCallback(() => {
if (isLoaded) {
show()
}
}, [isLoaded, show])
// Reset ad
const handleReset = useCallback(() => {
reset()
}, [reset])
return (
{/* Status display */}
Ready: {isReady ? 'Yes' : 'No'}Loaded: {isLoaded ? 'Yes' : 'No'}Opened: {isOpened ? 'Yes' : 'No'}Closed: {isClosed ? 'Yes' : 'No'}Clicked: {isClicked ? 'Yes' : 'No'}
{errorCode && (
Error: {errorCode}
)}
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
gap: 16,
},
statusContainer: {
marginTop: 24,
padding: 16,
backgroundColor: '#f0f0f0',
borderRadius: 8,
},
error: {
color: 'red',
marginTop: 16,
},
})
export default InterstitialAdScreen
```
***
## Test Unit ID
Be sure to use test unit IDs in development and test environments.
```typescript theme={null}
// For testing
const TEST_UNIT_ID = 'PUBLIC_TEST_UNIT_ID_INTERSTITIAL'
// Separate dev/production environments
const unitId = __DEV__
? 'PUBLIC_TEST_UNIT_ID_INTERSTITIAL'
: 'YOUR_PRODUCTION_UNIT_ID'
```
You must use actual unit IDs in production builds. Revenue from test unit IDs will not be settled.
***
## Error Handling
Errors that may occur during ad loading and display should be handled appropriately.
### Error Codes
| Error Code | Description | Response |
| - | - | - |
| `AdropErrorCode.network` | Network error | Check network connection and retry |
| `AdropErrorCode.internal` | Internal error | Retry or contact support |
| `AdropErrorCode.initialize` | SDK initialization failed | Check SDK initialization code |
| `AdropErrorCode.invalidUnit` | Invalid unit ID | Check unit ID |
| `AdropErrorCode.adNoFill` | Ad inventory shortage | Normal, retry later |
| `AdropErrorCode.adLoading` | Already loading | Retry after loading completes |
| `AdropErrorCode.adEmpty` | No loaded ad | Show after loading ad |
| `AdropErrorCode.adShown` | Already shown ad | Need to load new ad |
### Error Handling Example
```typescript theme={null}
const { load, show, errorCode, isLoaded } = useAdropInterstitialAd(unitId)
useEffect(() => {
if (errorCode) {
switch (errorCode) {
case AdropErrorCode.network:
console.log('Network error: Check connection')
// Retry after 30 seconds
setTimeout(() => load(), 30000)
break
case AdropErrorCode.adNoFill:
console.log('No ads available')
// Continue without ad
break
case AdropErrorCode.invalidUnit:
console.error('Invalid unit ID')
break
default:
console.log('Ad error:', errorCode)
break
}
}
}, [errorCode, load])
```
***
## Best Practices
### 1. Appropriate Display Timing
Show ads at natural transition points in your app.
```typescript theme={null}
// ✅ Good: Game level transition
const handleLevelComplete = () => {
saveProgress()
if (isLoaded) {
show() // Natural transition point
}
navigateToNextLevel()
}
// ✅ Good: After reading content
const handleArticleFinished = () => {
if (isLoaded) {
show()
}
}
// ❌ Bad: During user action
const handleButtonClick = () => {
show() // Disrupts user experience
performAction()
}
```
### 2. Preloading
Preload ads to minimize user wait time.
```typescript theme={null}
const InterstitialAdScreen: React.FC = () => {
const { load, show, isLoaded, isClosed } = useAdropInterstitialAd(unitId)
// Preload ad on component mount
useEffect(() => {
load()
}, [load])
// Preload next ad after closing
useEffect(() => {
if (isClosed) {
load()
}
}, [isClosed, load])
return (
// ...
)
}
```
### 3. Frequency Limiting
Limit how often ads are shown.
```typescript theme={null}
const InterstitialAdScreen: React.FC = () => {
const { show, isLoaded } = useAdropInterstitialAd(unitId)
const [lastShownTime, setLastShownTime] = useState(0)
const MIN_INTERVAL = 3 * 60 * 1000 // 3 minutes
const showAdWithFrequencyLimit = useCallback(() => {
const now = Date.now()
if (now - lastShownTime < MIN_INTERVAL) {
console.log('Ad display interval too short')
return
}
if (isLoaded) {
show()
setLastShownTime(now)
}
}, [isLoaded, show, lastShownTime])
return (
// ...
)
}
```
### 4. Game Pause Handling
Use listeners to manage game state in class method.
```typescript theme={null}
const listener: AdropListener = useMemo(() => ({
onAdWillPresentFullScreen: (ad) => {
// Pause game
pauseGame()
pauseBackgroundMusic()
},
onAdDidDismissFullScreen: (ad) => {
// Resume game
resumeGame()
resumeBackgroundMusic()
},
}), [])
```
### 5. Memory Management
Always release resources on component unmount.
```typescript theme={null}
// Hook method - automatic management
const { load, show } = useAdropInterstitialAd(unitId)
// Class method - manual cleanup required
useEffect(() => {
const ad = new AdropInterstitialAd(unitId)
setInterstitialAd(ad)
return () => {
ad.destroy() // Required!
}
}, [unitId])
```
***
## Platform Considerations
### iOS
* On iOS, ads automatically close when the app goes to background
* `onAdDidDismissFullScreen` callback is called
### Android
* Ads can be closed with the back button on Android
* Hardware acceleration may be required
***
## Backfill Ads
To use backfill ads, you must have the backfill dependency added to your native platforms (Android/iOS).
When backfill ads are enabled, the SDK automatically falls back to backfill ads when no direct ad is available. You can handle backfill-specific error codes in the listener.
```tsx theme={null}
interstitialAd.listener = {
onAdReceived: (ad) => {
console.log('Ad loaded:', ad.creativeId)
},
onAdFailedToReceive: (ad, errorCode) => {
switch (errorCode) {
case AdropErrorCode.adNoFill:
console.log('No direct ad available, requesting backfill...')
break
case AdropErrorCode.backfillNoFill:
console.log('No backfill ad available either')
break
default:
console.log('Ad load failed:', errorCode)
}
},
}
```
***
## Next Steps
Improve user engagement with rewarded ads
Implement banner ads
Provide natural ad experience with native ads
Implement popup ads
# Native Ads
Source: https://docs.adrop.io/sdk/react-native/native
Guide to implementing native ads in React Native apps.
## Overview
Native ads are an ad format that allows you to freely customize the ad UI to match your app's design. You can individually place ad elements such as media (images, videos), title, description, and CTA button to provide a natural user experience.
### Key Features
* **Complete UI Customization**: Freely configure ad layout to match your app's design system
* **Support for Various Media**: Support for image and video ad materials
* **Flexible Click Handling**: Full click or custom click handling available
* **Profile Information Display**: Support for advertiser profile logo and name
* **Backfill Ad Support**: Can check if it's a backfill ad
***
## AdropNativeAd Class
The main class for loading and managing native ads.
### Constructor
```typescript theme={null}
new AdropNativeAd(
unitId: string,
useCustomClick?: boolean,
preferredAdChoicesPosition?: AdropAdChoicesPosition
)
```
Ad unit ID (issued from console)
Whether to use custom click handling
* `true`: Developer implements click handling directly (can use video ad controller)
* `false`: AdropNativeAdView automatically handles all click events
AdChoices badge position for backfill native ads
### Methods
| Method | Description |
| - | - |
| `load()` | Request and load the ad |
| `destroy()` | Release ad resources |
### Properties
| Property | Type | Description |
| - | - | - |
| `isLoaded` | `boolean` | Whether ad is loaded (read-only) |
| `unitId` | `string` | Ad unit ID (read-only) |
| `useCustomClick` | `boolean` | Whether custom click handling is used (read-only) |
| `creativeId` | `string` | Ad creative ID (read-only) |
| `txId` | `string` | Transaction ID (read-only) |
| `campaignId` | `string` | Campaign ID (read-only) |
| `isBackfilled` | `boolean` | Whether it's a backfill ad (read-only) |
| `isVideoAd` | `boolean` | Whether it's a video ad (read-only) |
| `browserTarget` | `BrowserTarget` | Browser target setting (read-only) |
| `properties` | `AdropNativeProperties` | Ad properties information (read-only) |
| `listener` | `AdropNativeAdListener` | Ad event listener |
***
## AdropNativeAdView Component
Container component to display native ads.
### Props
```typescript theme={null}
interface Props extends ViewProps {
nativeAd?: AdropNativeAd
}
```
Native ad instance to display
### Usage Example
```tsx theme={null}
{/* Ad elements */}
```
`AdropNativeAdView` provides ad context, so all ad element views must be placed inside this component.
***
## Native Ad Element Views
### AdropProfileLogoView
Image component to display advertiser profile logo.
```tsx theme={null}
```
**Props**: `Omit` (React Native Image component properties, `source` is auto-populated)
### AdropProfileNameView
Text component to display advertiser profile name.
```tsx theme={null}
```
**Props**: `TextProps` (React Native Text component properties)
### AdropHeadLineView
Text component to display ad title.
```tsx theme={null}
```
**Props**: `TextProps` (React Native Text component properties)
### AdropBodyView
Text component to display ad body text.
```tsx theme={null}
```
**Props**: `TextProps` (React Native Text component properties)
### AdropMediaView
Media component to display ad image or video.
```tsx theme={null}
```
**Props**: `ViewProps` (React Native View component properties)
For **video native creatives**, render `properties.creative` (the HTML payload) inside a `WebView`. Passing `properties.asset` directly into `react-native-video` or any other custom player bypasses the SDK's video tracking pipeline, so **VTR is not measured** and `onAdVideoStart` / `onAdVideoEnd` never fire. See [Video Tracking and VTR](#video-tracking-and-vtr).
### AdropCallToActionView
Text component to display call-to-action (CTA) message.
```tsx theme={null}
```
**Props**: `TextProps` (React Native Text component properties)
### AdropAdvertiserView
Text component to display advertiser name.
```tsx theme={null}
```
**Props**: `TextProps` (React Native Text component properties)
### AdropIconView
Image component to display ad icon.
```tsx theme={null}
```
**Props**: `Omit` (React Native Image component properties, `source` is auto-populated)
***
## AdropNativeAdListener Interface
Listener interface for handling ad events.
```typescript theme={null}
interface AdropNativeAdListener {
onAdReceived?: (ad: AdropNativeAd) => void
onAdClicked?: (ad: AdropNativeAd) => void
onAdImpression?: (ad: AdropNativeAd) => void
onAdFailedToReceive?: (ad: AdropNativeAd, errorCode?: any) => void
onAdVideoStart?: (ad: AdropNativeAd) => void
onAdVideoEnd?: (ad: AdropNativeAd) => void
}
```
| Callback Method | Description |
| - | - |
| `onAdReceived` | Called when ad reception is successful |
| `onAdClicked` | Called when ad is clicked |
| `onAdImpression` | Called when ad is displayed |
| `onAdFailedToReceive` | Called when ad reception fails |
| `onAdVideoStart` | Called when a video ad starts playing |
| `onAdVideoEnd` | Called when a video ad finishes playing |
***
## AdropNativeProperties Type
Type containing ad properties information.
```typescript theme={null}
type AdropNativeProperties = {
icon?: string // Icon image URL
cover?: string // Cover image URL
headline?: string // Ad title
body?: string // Ad body text
creative?: string // Ad creative HTML (for WebView rendering)
asset?: string // Asset information
destinationURL?: string // Destination URL
advertiserURL?: string // Advertiser URL
accountTag?: string // Account tag
creativeTag?: string // Creative tag
advertiser?: string // Advertiser name
callToAction?: string // Call to action message
profile?: AdropNativeProfile // Profile information
extra?: Record // Additional information
isBackfilled?: boolean // Whether it's a backfill ad
}
```
***
## AdropNativeProfile Type
Type containing advertiser profile information.
```typescript theme={null}
type AdropNativeProfile = {
displayName: string // Profile display name
displayLogo: string // Profile logo image URL
}
```
***
## Implementation Example
Here's a complete example of implementing native ads.
```tsx theme={null}
import React, { useCallback, useEffect, useMemo, useState } from 'react'
import {
AdropBodyView,
AdropHeadLineView,
AdropMediaView,
AdropNativeAd,
AdropNativeAdView,
AdropProfileLogoView,
AdropProfileNameView,
} from 'adrop-ads-react-native'
import type { AdropNativeAdListener } from 'adrop-ads-react-native'
import { WebView } from 'react-native-webview'
import {
Button,
Dimensions,
ScrollView,
StyleSheet,
Text,
View,
Linking,
Platform,
} from 'react-native'
const NativeAdExample: React.FC = () => {
const [nativeAd, setNativeAd] = useState()
const [isLoaded, setIsLoaded] = useState(false)
const [errorCode, setErrorCode] = useState('')
// URL opening handler
const openUrl = useCallback((url: string) => {
Linking.openURL(url).catch((err) =>
console.error('Failed to open URL:', err)
)
}, [])
// Ad event listener
const listener = useMemo(
(): AdropNativeAdListener => ({
onAdReceived: (ad) => {
console.log('Ad received:', ad.unitId, ad.properties)
setIsLoaded(true)
setErrorCode('')
},
onAdFailedToReceive: (_, error) => {
console.log('Ad reception failed:', error)
setErrorCode(error)
},
onAdClicked: (ad) => {
console.log('Ad clicked:', ad.unitId)
},
onAdImpression: (ad) => {
console.log('Ad impression:', ad.unitId)
},
}),
[]
)
// Initialize ad
const initialize = useCallback(
(unitId: string) => {
const adropNativeAd = new AdropNativeAd(unitId)
adropNativeAd.listener = listener
setNativeAd((prev) => {
prev?.destroy()
return adropNativeAd
})
setIsLoaded(false)
setErrorCode('')
},
[listener]
)
// Initialize ad on component mount
useEffect(() => {
initialize('YOUR_UNIT_ID')
// Release ad on component unmount
return () => {
nativeAd?.destroy()
}
}, [])
// Load ad
const load = () => nativeAd?.load()
// Render ad view
const adView = useMemo(() => {
if (!isLoaded) return null
return (
{/* Profile area */}
{/* Ad title */}
{/* Ad body */}
{/* Media view for backfilled ads or non-video ads, WebView for video ads */}
{!nativeAd?.isVideoAd || nativeAd?.isBackfilled ? (
// Backfill ad or non-video ad: Use AdropMediaView
) : (
// Video ad: Render with WebView
{
// Android WebView event
if (
event.url &&
event.url !== 'about:blank' &&
!event.url.startsWith('data:')
) {
openUrl(event.url)
}
}}
onOpenWindow={(event) => {
// iOS WebView event (window.open)
if (event.nativeEvent?.targetUrl) {
openUrl(event.nativeEvent.targetUrl)
}
}}
/>
)}
)
}, [isLoaded, nativeAd, openUrl])
return (
{/* Ad load button */}
{/* Ad view */}
{adView}
{/* Error display */}
{errorCode && (
Error: {errorCode}
)}
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
paddingVertical: 5,
paddingHorizontal: 16,
},
button: {
marginVertical: 8,
},
adContainer: {
paddingHorizontal: 16,
},
rowContainer: {
flexDirection: 'row',
justifyContent: 'flex-start',
alignItems: 'center',
marginBottom: 8,
},
profileLogo: {
width: 32,
height: 32,
marginRight: 8,
borderRadius: 16,
},
profileName: {
fontSize: 14,
fontWeight: 'bold',
color: 'black',
},
headline: {
fontSize: 16,
fontWeight: 'bold',
color: 'black',
marginTop: 8,
},
body: {
fontSize: 14,
color: 'black',
marginVertical: 8,
},
media: {
width: '100%',
height: 360,
marginTop: 8,
},
error: {
color: 'red',
marginVertical: 8,
},
})
export default NativeAdExample
```
***
## Test Unit IDs
Use the following test unit IDs during development and testing.
| Platform | Format | Test Unit ID |
| - | - | - |
| Android/iOS | Native (Image) | `PUBLIC_TEST_UNIT_ID_NATIVE` |
| Android/iOS | Native Video (16:9) | `PUBLIC_TEST_UNIT_ID_NATIVE_VIDEO_16_9` |
| Android/iOS | Native Video (9:16) | `PUBLIC_TEST_UNIT_ID_NATIVE_VIDEO_9_16` |
Be sure to replace with actual unit IDs before production deployment.
***
## Error Handling
When ad loading fails, you can receive error codes in the `onAdFailedToReceive` callback.
### Common Error Codes
| Error Code | Description |
| - | - |
| `AdropErrorCode.network` | Network error |
| `AdropErrorCode.internal` | Internal error |
| `AdropErrorCode.initialize` | Initialization error |
| `AdropErrorCode.invalidUnit` | Invalid unit ID |
| `AdropErrorCode.adNoFill` | No ads available |
| `AdropErrorCode.adLoading` | Ad is loading |
| `AdropErrorCode.backfillNoFill` | No backfill ads available |
### Error Handling Example
```typescript theme={null}
const listener: AdropNativeAdListener = {
onAdFailedToReceive: (ad, errorCode) => {
switch (errorCode) {
case AdropErrorCode.adNoFill:
console.log('No ads available - hide ad area')
break
case AdropErrorCode.network:
console.log('Network error - retry')
setTimeout(() => ad.load(), 3000)
break
default:
console.log('Ad loading failed:', errorCode)
}
}
}
```
***
## Best Practices
### 1. Resource Cleanup
Always release ad resources when component unmounts.
```typescript theme={null}
useEffect(() => {
const ad = new AdropNativeAd('YOUR_UNIT_ID')
setNativeAd(ad)
return () => {
ad.destroy()
}
}, [])
```
### 2. Video Ad and Backfill Ad Handling
Choose appropriate rendering method based on whether it's a video ad or backfill ad.
```tsx theme={null}
{!nativeAd?.isVideoAd || nativeAd?.isBackfilled ? (
) : (
)}
```
### 3. URL Handling
Handle URL clicks in WebView to open in external browser.
```tsx theme={null}
{
if (event.url && event.url !== 'about:blank') {
Linking.openURL(event.url)
}
}}
/>
```
### 4. Selective Use of Ad Elements
Selectively use only the ad elements needed to match your app's design.
```tsx theme={null}
{/* Minimal configuration: Display only media and title */}
```
***
## Video Tracking and VTR
Adrop measures video metrics for native creatives — including impression-to-completion (VTR), `onAdVideoStart`, and `onAdVideoEnd` — only when the video is rendered through the SDK's media surface. On React Native, that means rendering the HTML creative (`properties.creative`) inside a `WebView`, as shown in the [Implementation Example](#implementation-example).
The `properties.asset` field returns a URL pointing to the underlying image or video file. It is exposed as supplementary metadata (for example, for thumbnails or your own analytics); it is **not** intended to drive playback.
Do not feed the `properties.asset` URL into a custom video player (`react-native-video`, `expo-av`, or any third-party player) for a video native ad. When the SDK's media surface is bypassed:
* **VTR (Video Through Rate) is not collected** for that placement.
* `onAdVideoStart` / `onAdVideoEnd` callbacks **never fire**.
* Aggregate video performance for the unit will under-report or report zero.
Click (`onAdClicked`) and impression (`onAdImpression`) tracking still work because they are wired to `AdropNativeAdView`, but the video-specific signals are lost.
### Recommended pattern
For video creatives, render the HTML creative inside a `WebView` placed inside `AdropNativeAdView`:
```tsx theme={null}
{!nativeAd?.isVideoAd || nativeAd?.isBackfilled ? (
) : (
)}
```
The `isVideoAd` and `isBackfilled` flags on `AdropNativeAd` let you branch between `` (image or backfill creative) and `` (direct video creative) without inspecting the asset URL yourself.
If a placement absolutely requires a custom player and you accept that VTR will not be measured, you can still surface click and impression attribution by keeping the ad bound to `AdropNativeAdView`. In that case, treat the placement as **non-VTR inventory** in your internal reporting and avoid mixing it with SDK-measured video performance.
***
## AdChoices Position
For backfill native ads, you can choose which corner displays the AdChoices badge. Pass `preferredAdChoicesPosition` as the third argument to the `AdropNativeAd` constructor.
```tsx theme={null}
import { AdropNativeAd, AdropAdChoicesPosition } from 'adrop-ads-react-native'
const nativeAd = new AdropNativeAd(
'YOUR_UNIT_ID',
false, // useCustomClick
AdropAdChoicesPosition.bottomRight
)
nativeAd.listener = {
onAdReceived: (ad) => console.log('Received'),
onAdFailedToReceive: (ad, errorCode) => console.log('Failed', errorCode),
}
nativeAd.load()
```
| Value | Description |
| - | - |
| `AdropAdChoicesPosition.topLeft` | Top-left corner |
| `AdropAdChoicesPosition.topRight` | Top-right corner (default) |
| `AdropAdChoicesPosition.bottomLeft` | Bottom-left corner |
| `AdropAdChoicesPosition.bottomRight` | Bottom-right corner |
This setting is a preferred hint for the backfill network and applies only to backfill native ads — direct ads are not affected. The backfill network may override the position depending on its policy.
***
## Next Steps
Implement banner ads
Implement interstitial ads
Implement popup ads
Implement rewarded ads
# Getting Started
Source: https://docs.adrop.io/sdk/react-native/overview
Guide to integrating Adrop SDK into your React Native app.
## Overview
The Adrop React Native SDK allows you to easily integrate various ad formats into your React Native app.
### Supported Ad Formats
| Format | Description |
| - | - |
| Banner Ads | Rectangular ads displayed in a portion of the screen |
| Native Ads | Ads that blend naturally with app content |
| Interstitial Ads | Full-screen ads |
| Rewarded Ads | Full-screen ads that provide rewards |
| Popup Ads | Ads displayed in popup format |
| Splash Ads | Ads displayed on app launch screen |
***
## Requirements
### React Native
* React Native **0.71** or higher
### Android
* API level **23** (Android 6.0) or higher
* `compileSdkVersion` **34**
* Kotlin **2.1.0** or higher
* Gradle **8.7** or higher
### iOS
* iOS **13.0** or higher
* Swift **5.0** or higher
***
## Installation
### 1. Install Package
```bash npm theme={null}
npm install adrop-ads-react-native
```
```bash yarn theme={null}
yarn add adrop-ads-react-native
```
### 2. Android Setup
Add the backfill ad repository to `android/settings.gradle.kts`.
```kotlin android/settings.gradle.kts theme={null}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven { url = uri("https://artifact.bytedance.com/repository/pangle") }
}
}
```
Add the Kotlin plugin and backfill dependency to `android/app/build.gradle`.
```gradle android/app/build.gradle theme={null}
apply plugin: "kotlin-android"
dependencies {
implementation 'io.adrop:adrop-ads-backfill:1.15.0'
}
```
Add the backfill App ID to `android/app/src/main/AndroidManifest.xml`.
```xml android/app/src/main/AndroidManifest.xml theme={null}
```
Please contact Adrop to get your App ID.
### 3. iOS Setup
Modify `ios/Podfile`.
```ruby ios/Podfile theme={null}
use_frameworks!
target 'YourApp' do
# Other dependencies...
# Backfill ad SDK
pod 'adrop-ads-backfill'
end
```
Install pods.
```bash theme={null}
cd ios && pod install --repo-update && cd ..
```
### 4. Info.plist Configuration
Add the following settings to `ios/YourApp/Info.plist` for backfill ads.
* **GADApplicationIdentifier**: App ID for backfill ads
* **SKAdNetworkItems**: Required for ad conversion tracking on iOS 14+
```xml Info.plist theme={null}
GADApplicationIdentifierca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyySKAdNetworkItemsSKAdNetworkIdentifiercstr6suwn9.skadnetworkSKAdNetworkIdentifier4fzdc2evr5.skadnetworkSKAdNetworkIdentifier4pfyvq9l8r.skadnetworkSKAdNetworkIdentifier2fnua5tdw4.skadnetworkSKAdNetworkIdentifierydx93a7ass.skadnetworkSKAdNetworkIdentifier5a6flpkh64.skadnetworkSKAdNetworkIdentifierp78axxw29g.skadnetworkSKAdNetworkIdentifierv72qych5uu.skadnetworkSKAdNetworkIdentifierludvb6z3bs.skadnetworkSKAdNetworkIdentifiercp8zw746q7.skadnetworkSKAdNetworkIdentifierv9wttpbfk9.skadnetworkSKAdNetworkIdentifierlr83yxwka7.skadnetworkSKAdNetworkIdentifier578prtvx9j.skadnetworkSKAdNetworkIdentifier7ug5zh24hu.skadnetworkSKAdNetworkIdentifierwg4vff78zm.skadnetworkSKAdNetworkIdentifiermj797d8u6f.skadnetworkSKAdNetworkIdentifier8s468mfl3y.skadnetworkSKAdNetworkIdentifier4dzt52r2t5.skadnetworkSKAdNetworkIdentifiervhf287vqwu.skadnetworkSKAdNetworkIdentifierc6k4g5qg8m.skadnetworkSKAdNetworkIdentifier5tjdwbrq8w.skadnetworkSKAdNetworkIdentifier294l99pt4k.skadnetworkSKAdNetworkIdentifiermqn7fxpca7.skadnetworkSKAdNetworkIdentifierg6gcrrvk4p.skadnetworkSKAdNetworkIdentifiertl55sbb4fm.skadnetworkSKAdNetworkIdentifiers39g8k73mm.skadnetworkSKAdNetworkIdentifiera2p9lx4jpn.skadnetworkSKAdNetworkIdentifierk6y4y55b64.skadnetworkSKAdNetworkIdentifier488r3q3dtq.skadnetworkSKAdNetworkIdentifier9rd848q2bz.skadnetworkSKAdNetworkIdentifierglqzh8vgby.skadnetworkSKAdNetworkIdentifier5lm9lj6jb7.skadnetworkSKAdNetworkIdentifier97r2b46745.skadnetworkSKAdNetworkIdentifierzmvfpc5aq8.skadnetworkSKAdNetworkIdentifier3sh42y64q3.skadnetworkSKAdNetworkIdentifiermlmmfzh3r3.skadnetworkSKAdNetworkIdentifier3rd42ekr43.skadnetworkSKAdNetworkIdentifier2u9pt9hc89.skadnetworkSKAdNetworkIdentifierm8dbw4sv7c.skadnetworkSKAdNetworkIdentifier238da6jt44.skadnetworkSKAdNetworkIdentifier4468km3ulz.skadnetworkSKAdNetworkIdentifier4w7y6s5ca2.skadnetworkSKAdNetworkIdentifiera8cz6cu7e5.skadnetworkSKAdNetworkIdentifier77y3x8wds4.skadnetworkSKAdNetworkIdentifiern9x2a789qt.skadnetworkSKAdNetworkIdentifierhs6bdukanm.skadnetworkSKAdNetworkIdentifierf7s53z58qe.skadnetworkSKAdNetworkIdentifier9t245vhmpl.skadnetworkSKAdNetworkIdentifiermp6xlyr22a.skadnetworkSKAdNetworkIdentifierprcb7njmu6.skadnetworkSKAdNetworkIdentifierkbd757ywx3.skadnetworkSKAdNetworkIdentifierk674qkevps.skadnetworkSKAdNetworkIdentifiert38b2kh725.skadnetworkSKAdNetworkIdentifierfeyaarzu9v.skadnetworkSKAdNetworkIdentifier5f5u5tfb26.skadnetworkSKAdNetworkIdentifier424m5254lk.skadnetworkSKAdNetworkIdentifiere5fvkxwrpn.skadnetworkSKAdNetworkIdentifier3qy4746246.skadnetworkSKAdNetworkIdentifierklf5c3l5u5.skadnetworkSKAdNetworkIdentifierzq492l623r.skadnetworkSKAdNetworkIdentifier22mmun2rn5.skadnetworkSKAdNetworkIdentifierav6w8kgt66.skadnetworkSKAdNetworkIdentifierv79kvwwj4g.skadnetworkSKAdNetworkIdentifier44jx6755aq.skadnetworkSKAdNetworkIdentifier32z4fx6l9h.skadnetworkSKAdNetworkIdentifierw9q455wk68.skadnetworkSKAdNetworkIdentifierxga6mpmplv.skadnetworkSKAdNetworkIdentifierf38h382jlk.skadnetworkSKAdNetworkIdentifieryclnxrl5pm.skadnetworkSKAdNetworkIdentifierx44k69ngh6.skadnetworkSKAdNetworkIdentifierppxm28t8ap.skadnetworkSKAdNetworkIdentifierf73kdq92p3.skadnetworkSKAdNetworkIdentifier5l3tpt7t6e.skadnetworkSKAdNetworkIdentifierwzmmz9fp6w.skadnetworkSKAdNetworkIdentifierpwa73g5rt2.skadnetworkSKAdNetworkIdentifier9nlqeag3gk.skadnetworkSKAdNetworkIdentifier6yxyv74ff7.skadnetworkSKAdNetworkIdentifieruw77j35x4d.skadnetworkSKAdNetworkIdentifier54nzkqm89y.skadnetworkSKAdNetworkIdentifier44n7hlldy6.skadnetworkSKAdNetworkIdentifier6964rsfnh4.skadnetworkSKAdNetworkIdentifier6g9af3uyq4.skadnetworkSKAdNetworkIdentifier6v7lgmsu45.skadnetworkSKAdNetworkIdentifier7fmhfwg9en.skadnetworkSKAdNetworkIdentifiera7xqa6mtl2.skadnetworkSKAdNetworkIdentifier84993kbrcf.skadnetworkSKAdNetworkIdentifierbxvub5ada5.skadnetworkSKAdNetworkIdentifier79pbpufp6p.skadnetworkSKAdNetworkIdentifier9vvzujtq5s.skadnetworkSKAdNetworkIdentifier7rz58n8ntl.skadnetworkSKAdNetworkIdentifier3qcr597p9d.skadnetworkSKAdNetworkIdentifier737z793b9f.skadnetworkSKAdNetworkIdentifier89z7zv988g.skadnetworkSKAdNetworkIdentifierc3frkrj4fj.skadnetworkSKAdNetworkIdentifier275upjj5gd.skadnetworkSKAdNetworkIdentifier3l6bd9hu43.skadnetworkSKAdNetworkIdentifier52fl2v3hgk.skadnetworkSKAdNetworkIdentifier6p4ks3rnbw.skadnetworkSKAdNetworkIdentifierapzhy3va96.skadnetworkSKAdNetworkIdentifier9b89h5y424.skadnetworkSKAdNetworkIdentifier523jb4fst2.skadnetworkSKAdNetworkIdentifierb9bk5wbcq9.skadnetworkSKAdNetworkIdentifiercg4yq2srnc.skadnetworkSKAdNetworkIdentifiercj5566h2ga.skadnetworkSKAdNetworkIdentifier24zw6aqk47.skadnetworkSKAdNetworkIdentifier74b6s63p6l.skadnetworkSKAdNetworkIdentifier8m87ys6875.skadnetworkSKAdNetworkIdentifier24t9a8vw3c.skadnetworkSKAdNetworkIdentifier6xzpu9s2p8.skadnetworkSKAdNetworkIdentifiercs644xg564.skadnetworkSKAdNetworkIdentifierdbu4b84rxf.skadnetworkSKAdNetworkIdentifierejvt5qm6ak.skadnetworkSKAdNetworkIdentifiermtkv5xtk9e.skadnetworkSKAdNetworkIdentifierkbmxgpxpgc.skadnetworkSKAdNetworkIdentifierhb56zgv37p.skadnetworkSKAdNetworkIdentifierpwdxu55a5a.skadnetworkSKAdNetworkIdentifierr45fhb6rf7.skadnetworkSKAdNetworkIdentifiert6d3zquu66.skadnetworkSKAdNetworkIdentifieru679fj5vs4.skadnetworkSKAdNetworkIdentifierdzg6xy7pwj.skadnetworkSKAdNetworkIdentifierg28c52eehv.skadnetworkSKAdNetworkIdentifiermls7yz5dvl.skadnetworkSKAdNetworkIdentifiervcra2ehyfk.skadnetworkSKAdNetworkIdentifierrx5hdcabgc.skadnetworkSKAdNetworkIdentifierqwpu75vrh2.skadnetworkSKAdNetworkIdentifierrvh3l7un93.skadnetworkSKAdNetworkIdentifierx5l83yy675.skadnetworkSKAdNetworkIdentifierm5mvw97r93.skadnetworkSKAdNetworkIdentifierxy9t38ct57.skadnetworkSKAdNetworkIdentifierg2y4y55b64.skadnetworkSKAdNetworkIdentifiergta9lk7p23.skadnetworkSKAdNetworkIdentifiern6fk4nfna4.skadnetworkSKAdNetworkIdentifierqu637u8glc.skadnetworkSKAdNetworkIdentifiery5ghdn5j9k.skadnetworkSKAdNetworkIdentifiery45688jllp.skadnetworkSKAdNetworkIdentifierggvn48r87g.skadnetworkSKAdNetworkIdentifierm297p6643m.skadnetworkSKAdNetworkIdentifierqqp299437r.skadnetworkSKAdNetworkIdentifierhdw39hrw9y.skadnetworkSKAdNetworkIdentifierkrvm3zuq6h.skadnetworkSKAdNetworkIdentifiercwn433xbcr.skadnetworkSKAdNetworkIdentifierns5j362hk7.skadnetworkSKAdNetworkIdentifiersu67r6k2v3.skadnetworkSKAdNetworkIdentifierz959bm4gru.skadnetworkSKAdNetworkIdentifierx8uqf25wch.skadnetworkSKAdNetworkIdentifiern38lu8286q.skadnetworkSKAdNetworkIdentifierfz2k2k5tej.skadnetworkSKAdNetworkIdentifierecpz2srf59.skadnetworkSKAdNetworkIdentifierbvpn9ufa9b.skadnetworkSKAdNetworkIdentifier6rd35atwn8.skadnetworkSKAdNetworkIdentifierln5gz23vtd.skadnetworkSKAdNetworkIdentifiertmhh9296z4.skadnetworkSKAdNetworkIdentifiersczv5946wb.skadnetworkSKAdNetworkIdentifierdkc879ngq3.skadnetworkSKAdNetworkIdentifier87u5trcl3r.skadnetworkSKAdNetworkIdentifierfq6vru337s.skadnetworkSKAdNetworkIdentifierf2zub97jtl.skadnetworkSKAdNetworkIdentifierthzdn4h5nc.adattributionkitSKAdNetworkIdentifierraa6f494kr.adattributionkitSKAdNetworkIdentifier6lz2ygh3q6.adattributionkitSKAdNetworkIdentifierm2jqnlggk3.adattributionkitSKAdNetworkIdentifierpg7ctvrt6f.adattributionkitSKAdNetworkIdentifier77y3x8wds4.adattributionkitSKAdNetworkIdentifier55644vm79v.skadnetworkSKAdNetworkIdentifiernzq8sh4pbs.skadnetworkSKAdNetworkIdentifier55y65gfgn7.skadnetworkSKAdNetworkIdentifier252b5q8x7y.skadnetworkSKAdNetworkIdentifier47vhws6wlr.skadnetworkSKAdNetworkIdentifier4mn522wn87.skadnetworkSKAdNetworkIdentifier577p5t736z.skadnetworkSKAdNetworkIdentifier7953jerfzd.skadnetworkSKAdNetworkIdentifier7bxrt786m8.skadnetworkSKAdNetworkIdentifier7fbxrn65az.skadnetworkSKAdNetworkIdentifier8c4e2ghe7u.skadnetworkSKAdNetworkIdentifier8r8llnkz5a.skadnetworkSKAdNetworkIdentifier9yg77x724h.skadnetworkSKAdNetworkIdentifierce8ybjwass.skadnetworkSKAdNetworkIdentifierdt3cjx1a9i.skadnetworkSKAdNetworkIdentifiereh6m2bh4zr.skadnetworkSKAdNetworkIdentifiergta8lk7p23.skadnetworkSKAdNetworkIdentifiergvmwg8q7h5.skadnetworkSKAdNetworkIdentifierhjevpa356n.skadnetworkSKAdNetworkIdentifierjk2fsx2rgz.skadnetworkSKAdNetworkIdentifiern66cz3y3bx.skadnetworkSKAdNetworkIdentifierpu4na253f3.skadnetworkSKAdNetworkIdentifierr8lj5b58b5.skadnetworkSKAdNetworkIdentifiers69wq72ugq.skadnetworkSKAdNetworkIdentifierv4nxqhlyqp.skadnetworkSKAdNetworkIdentifiervutu7akeur.skadnetworkSKAdNetworkIdentifierz24wtl6j62.skadnetworkSKAdNetworkIdentifierz4gj7hsk7h.skadnetworkSKAdNetworkIdentifierx8jxxk4ff5.skadnetworkSKAdNetworkIdentifierx8yj322td6.skadnetwork
```
If your Info.plist already contains a `SKAdNetworkItems` key, do not add a new one — merge only the array (``) entries. Duplicate keys cause iOS to use only the first array.
Please contact Adrop for the full SKAdNetworkItems list and App ID.
### 2. Create Config Plugin
Create a `plugins/withAdropService.js` file in the project root. This Expo config plugin automatically generates `adrop_service.json` in native projects when running `npx expo prebuild`.
* Android: `android/app/src/main/assets/adrop_service.json`
* iOS: `ios/{projectName}/adrop_service.json`
```javascript plugins/withAdropService.js theme={null}
const { withDangerousMod } = require('@expo/config-plugins')
const fs = require('fs')
const path = require('path')
function withAdropServiceAndroid(config, props) {
return withDangerousMod(config, [
'android',
async (config) => {
const projectRoot = config.modRequest.projectRoot
const androidAssetsPath = path.join(projectRoot, 'android', 'app', 'src', 'main', 'assets')
if (!fs.existsSync(androidAssetsPath)) fs.mkdirSync(androidAssetsPath, { recursive: true })
const serviceConfig = { app_key: props.android?.appKey || props.appKey || '' }
fs.writeFileSync(path.join(androidAssetsPath, 'adrop_service.json'), JSON.stringify(serviceConfig, null, 2))
return config
},
])
}
function withAdropServiceIOS(config, props) {
return withDangerousMod(config, [
'ios',
async (config) => {
const projectRoot = config.modRequest.projectRoot
const projectName = config.modRequest.projectName
const iosPath = path.join(projectRoot, 'ios', projectName)
if (!fs.existsSync(iosPath)) fs.mkdirSync(iosPath, { recursive: true })
const serviceConfig = { app_key: props.ios?.appKey || props.appKey || '' }
fs.writeFileSync(path.join(iosPath, 'adrop_service.json'), JSON.stringify(serviceConfig, null, 2))
return config
},
])
}
module.exports = function withAdropService(config, props = {}) {
config = withAdropServiceAndroid(config, props)
config = withAdropServiceIOS(config, props)
return config
}
```
### 3. Configure app.json
Register the following two plugins in the `plugins` section of `app.json`.
```json app.json theme={null}
{
"expo": {
"plugins": [
[
"expo-build-properties",
{
"android": { "compileSdkVersion": 35, "targetSdkVersion": 35, "minSdkVersion": 24 },
"ios": { "deploymentTarget": "15.1" }
}
],
[
"./plugins/withAdropService",
{
"android": { "appKey": "" },
"ios": { "appKey": "" }
}
]
]
}
}
```
* **expo-build-properties**: Requires Android `minSdkVersion` 24 or higher, iOS `deploymentTarget` 15.1 or higher
* **withAdropService**: Pass the `appKey` issued from the Adrop console for each platform
***
## Initialization
Initialize the SDK when your app starts. This is typically done in `App.tsx` or `index.js`.
```tsx App.tsx theme={null}
import { Adrop } from 'adrop-ads-react-native';
// Initialize on app start
Adrop.initialize(false); // false: test mode, true: production mode
```
### Initialization Options
```tsx theme={null}
Adrop.initialize(
production, // boolean: whether production mode
targetCountries, // string[]?: target country code array (optional)
useInAppBrowser // boolean?: whether to use iOS in-app browser (optional)
);
```
**Parameters**
| Parameter | Type | Required | Description |
| - | - | - | - |
| `production` | `boolean` | Y | `true`: production mode, `false`: test mode |
| `targetCountries` | `string[]` | N | Country codes to display ads (e.g., `['KR', 'US']`) |
| `useInAppBrowser` | `boolean` | N | Whether to use in-app browser on iOS (default: `false`) |
**Example**
```tsx theme={null}
// Basic initialization (test mode)
Adrop.initialize(false);
// Production mode + specific country targeting
Adrop.initialize(true, ['KR', 'JP', 'US']);
// Production mode + iOS in-app browser
Adrop.initialize(true, undefined, true);
```
***
## Theme Settings
Set the UI theme for ads.
```tsx theme={null}
import { Adrop, AdropTheme } from 'adrop-ads-react-native';
// Set theme
Adrop.setTheme(AdropTheme.auto); // Follow system settings
Adrop.setTheme(AdropTheme.light); // Light mode
Adrop.setTheme(AdropTheme.dark); // Dark mode
```
| Theme | Description |
| - | - |
| `AdropTheme.auto` | Automatically switch based on system settings |
| `AdropTheme.light` | Light mode |
| `AdropTheme.dark` | Dark mode |
***
## User Identifier Settings
Set the user identifier (UID) for targeted advertising.
```tsx theme={null}
import { Adrop } from 'adrop-ads-react-native';
Adrop.setUID('user_123');
```
UID is transmitted after being hashed with SHA-256. Do not directly pass personal information such as email or phone number.
***
## Test Unit IDs
Use the following test unit IDs during development and testing.
| Format | Test Unit ID |
| - | - |
| Banner (320x50) | `PUBLIC_TEST_UNIT_ID_320_50` |
| Banner (320x100) | `PUBLIC_TEST_UNIT_ID_320_100` |
| Native | `PUBLIC_TEST_UNIT_ID_NATIVE` |
| Interstitial | `PUBLIC_TEST_UNIT_ID_INTERSTITIAL` |
| Rewarded | `PUBLIC_TEST_UNIT_ID_REWARDED` |
| Popup (Bottom) | `PUBLIC_TEST_UNIT_ID_POPUP_BOTTOM` |
| Popup (Center) | `PUBLIC_TEST_UNIT_ID_POPUP_CENTER` |
| Popup Video (Bottom 16:9) | `PUBLIC_TEST_UNIT_ID_POPUP_BOTTOM_VIDEO_16_9` |
| Popup Video (Bottom 9:16) | `PUBLIC_TEST_UNIT_ID_POPUP_BOTTOM_VIDEO_9_16` |
| Popup Video (Center 16:9) | `PUBLIC_TEST_UNIT_ID_POPUP_CENTER_VIDEO_16_9` |
| Popup Video (Center 9:16) | `PUBLIC_TEST_UNIT_ID_POPUP_CENTER_VIDEO_9_16` |
| Splash | `PUBLIC_TEST_UNIT_ID_SPLASH` |
```tsx theme={null}
// Banner ad test
const testBannerUnitId = 'PUBLIC_TEST_UNIT_ID_320_50';
```
Be sure to replace with actual unit IDs before production deployment.
***
## Error Codes
Error codes that may occur in the SDK.
| Error Code | Description |
| - | - |
| `AdropErrorCode.network` | Network error |
| `AdropErrorCode.internal` | Internal error |
| `AdropErrorCode.initialize` | SDK initialization error |
| `AdropErrorCode.invalidUnit` | Invalid unit ID |
| `AdropErrorCode.notTargetCountry` | Not a target country |
| `AdropErrorCode.inactive` | Inactive ad |
| `AdropErrorCode.adNoFill` | No ads to display |
| `AdropErrorCode.adDuplicated` | Duplicate load request |
| `AdropErrorCode.adLoading` | Loading in progress |
| `AdropErrorCode.adEmpty` | Empty ad |
| `AdropErrorCode.adShown` | Ad already shown |
| `AdropErrorCode.adHideForToday` | Ad hidden for today |
| `AdropErrorCode.adLandscapeUnsupported` | Landscape mode not supported |
| `AdropErrorCode.backfillNoFill` | Backfill ad not available |
```tsx theme={null}
import { AdropErrorCode } from 'adrop-ads-react-native';
// Error handling example
if (errorCode === AdropErrorCode.adNoFill) {
console.log('No ads available to display.');
}
```
***
## Troubleshooting
### iOS Build Error
If you encounter Swift version compatibility issues, add the following to the `post_install` block in `ios/Podfile`.
```ruby ios/Podfile theme={null}
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['BUILD_LIBRARY_FOR_DISTRIBUTION'] = 'YES'
end
end
end
```
### Android Build Error
If the Kotlin version doesn't match, check the Kotlin version in `android/build.gradle`.
```gradle android/build.gradle theme={null}
buildscript {
ext {
kotlinVersion = '2.1.0'
}
}
```
***
## Next Steps
Implement banner ads
Implement native ads
Implement interstitial ads
Implement rewarded ads
Display web ads in WebView
GDPR/CCPA consent management
Implement popup ads
Configure splash ads
Set up audience targeting
Complete API reference
Full implementation examples
# Popup Ads
Source: https://docs.adrop.io/sdk/react-native/popup
Learn how to implement popup ads in your React Native app.
## Overview
Popup ads are an ad format that appears on screen at specific moments. They can be displayed at app launch, content load completion, specific events, etc. Users can dismiss by tapping the close button or selecting "Don't show today".
### Features
* Popup format displayed at center or bottom of screen
* Image and video ad support
* Close, dim (background) click, and "Don't show today" options
* Customizable background color, text color, etc.
* Non-intrusive yet effective ad experience
Use test unit IDs in development. See the [Test Unit IDs](#test-unit-ids) section.
***
## Implementation Steps
Popup ads are implemented in 4 steps:
1. **Initialize** - Create AdropPopupAd instance
2. **Set Listener** - Set listener for receiving ad events
3. **Load Ad** - Request and receive ad
4. **Show Ad** - Display ad on screen
***
## Basic Implementation
### AdropPopupAd Class
`AdropPopupAd` is the main class for managing popup ads.
```typescript theme={null}
import { AdropPopupAd, type AdropPopupAdColors } from 'adrop-ads-react-native';
const popupAd = new AdropPopupAd(
unitId: string,
colors?: AdropPopupAdColors,
useCustomClick?: boolean
);
```
#### Constructor Parameters
Popup ad unit ID. Use the unit ID created in the Ad Control console.
Options for customizing popup ad colors.
* `closeTextColor`: Close button text color
* `hideForTodayTextColor`: "Don't show today" text color
* `backgroundColor`: Popup background (dim) color
Whether to use custom click handling. When set to `true`, only the `onAdClicked` callback is called instead of opening the default browser on ad click.
### Basic Usage
```typescript theme={null}
import React, { useEffect, useState, useMemo } from 'react';
import { Button, View } from 'react-native';
import { AdropPopupAd, type AdropListener } from 'adrop-ads-react-native';
const App: React.FC = () => {
const [popupAd, setPopupAd] = useState();
const [isLoaded, setIsLoaded] = useState(false);
const listener: AdropListener = useMemo(() => ({
onAdReceived: (ad: AdropPopupAd) => {
console.log('Popup ad received');
setIsLoaded(true);
},
onAdFailedToReceive: (_: AdropPopupAd, errorCode: any) => {
console.log('Popup ad failed to receive:', errorCode);
},
onAdClicked: (ad: AdropPopupAd) => {
console.log('Popup ad clicked');
// Close popup on click
ad.close();
},
}), []);
useEffect(() => {
// 1. Create popup ad
const ad = new AdropPopupAd('YOUR_POPUP_UNIT_ID');
ad.listener = listener;
setPopupAd(ad);
// 2. Load ad
ad.load();
// Cleanup on unmount
return () => {
ad.destroy();
};
}, [listener]);
// 3. Show ad
const showAd = () => {
if (popupAd && isLoaded) {
popupAd.show();
}
};
return (
);
};
export default App;
```
***
## AdropListener Interface
Listener interface for receiving popup ad events.
### Recommended Callbacks
Called on successful ad reception. You can call `show()` at this point.
Called on ad reception failure. Check error code for failure reason.
### Optional Callbacks
Called when ad impression is recorded.
Called when user clicks the ad. You can call `ad.close()` in this callback to dismiss the popup.
Called just before popup ad is displayed.
Called immediately after popup ad is displayed.
Called just before popup ad is dismissed.
Called immediately after popup ad is dismissed. Good time to preload next ad.
Called on ad display failure. Check error code for failure reason.
Called when the user presses the back button while the ad is displayed. **Android only.**
Called when a video ad starts playing.
Called when a video ad finishes playing.
### Listener Implementation Example
```typescript theme={null}
const listener: AdropListener = useMemo(() => ({
// Ad received successfully
onAdReceived: (ad: AdropPopupAd) => {
console.log('Popup ad received');
console.log('Creative ID:', ad.creativeId);
console.log('Campaign ID:', ad.campaignId);
// Auto-show when ad is ready
ad.show();
},
// Ad reception failed
onAdFailedToReceive: (ad: AdropPopupAd, errorCode?: any) => {
console.log('Popup ad failed to receive:', errorCode);
},
// Ad impression
onAdImpression: (ad: AdropPopupAd) => {
console.log('Popup ad impression');
console.log('TX ID:', ad.txId);
console.log('Destination URL:', ad.destinationURL);
},
// Ad clicked
onAdClicked: (ad: AdropPopupAd) => {
console.log('Popup ad clicked:', ad.destinationURL);
// Close popup on click
ad.close();
},
// Just before popup is displayed
onAdWillPresentFullScreen: (ad: AdropPopupAd) => {
console.log('Popup ad about to display');
},
// Popup displayed
onAdDidPresentFullScreen: (ad: AdropPopupAd) => {
console.log('Popup ad displayed');
},
// Just before popup is dismissed
onAdWillDismissFullScreen: (ad: AdropPopupAd) => {
console.log('Popup ad about to dismiss');
},
// Popup dismissed
onAdDidDismissFullScreen: (ad: AdropPopupAd) => {
console.log('Popup ad dismissed');
// Preload next ad
loadNextAd();
},
// Popup display failed
onAdFailedToShowFullScreen: (ad: AdropPopupAd, errorCode?: any) => {
console.log('Popup ad failed to show:', errorCode);
},
// Video ad started
onAdVideoStart: (ad: AdropPopupAd) => {
console.log('Video ad started playing:', ad.creativeId);
},
// Video ad ended
onAdVideoEnd: (ad: AdropPopupAd) => {
console.log('Video ad finished playing:', ad.creativeId);
},
}), []);
```
***
## Methods
### load()
Requests and loads an ad.
```typescript theme={null}
popupAd.load();
```
### show()
Displays the loaded ad. Can only be called after `onAdReceived` callback is fired.
```typescript theme={null}
popupAd.show();
```
Only call `show()` when the ad is loaded (`isLoaded === true`).
### close()
Closes the currently displayed popup ad. Primarily used in the `onAdClicked` callback.
```typescript theme={null}
popupAd.close();
```
### destroy()
Cleans up the ad instance and releases resources. Must be called on component unmount.
```typescript theme={null}
useEffect(() => {
const ad = new AdropPopupAd('YOUR_UNIT_ID');
setPopupAd(ad);
return () => {
ad.destroy();
};
}, []);
```
***
## Ad Properties
The following properties are available on the popup ad object:
Ad unit ID
Whether the ad is loaded
Creative ID of the currently displayed ad
URL to navigate to on ad click
Transaction ID (for ad impression tracking)
Campaign ID
Browser target for ad click. `BrowserTarget.EXTERNAL` (0) opens the system browser, `BrowserTarget.INTERNAL` (1) opens an in-app browser.
***
## Customization
### Color Settings
Use `AdropPopupAdColors` type to customize popup ad colors.
```typescript theme={null}
import { AdropPopupAd, type AdropPopupAdColors } from 'adrop-ads-react-native';
// Define color options
const customColors: AdropPopupAdColors = {
closeTextColor: '#FFFFFF', // Close button color
hideForTodayTextColor: '#456789', // "Don't show today" color
backgroundColor: 'rgba(53, 255, 63, 0.3)', // Background (dim) color
};
// Apply colors when creating popup ad
const popupAd = new AdropPopupAd(
'YOUR_POPUP_UNIT_ID',
customColors
);
```
#### AdropPopupAdColors Type
Close button text color. Supports HEX color codes or RGBA format.
"Don't show today" text color. Supports HEX color codes or RGBA format.
Popup background (dim) color. Supports HEX color codes or RGBA format. Transparency can be adjusted.
### Custom Click Handling
To use custom handling instead of opening the default browser on ad click, use the `useCustomClick` parameter.
```typescript theme={null}
const listener: AdropListener = useMemo(() => ({
onAdReceived: (ad: AdropPopupAd) => {
ad.show();
},
onAdFailedToReceive: (_: AdropPopupAd, errorCode: any) => {
console.log('Ad failed to receive:', errorCode);
},
onAdClicked: (ad: AdropPopupAd) => {
// Custom click handling
console.log('Ad clicked:', ad.destinationURL);
// Handle URL as desired
// e.g., Open in in-app browser
openInAppBrowser(ad.destinationURL);
// Close popup
ad.close();
},
}), []);
// Set useCustomClick to true
const popupAd = new AdropPopupAd(
'YOUR_POPUP_UNIT_ID',
undefined, // colors
true // useCustomClick
);
popupAd.listener = listener;
```
***
## Test Unit IDs
Use the following test unit IDs during development and testing.
| Ad Type | Test Unit ID |
| - | - |
| Popup (Bottom Image) | `PUBLIC_TEST_UNIT_ID_POPUP_BOTTOM` |
| Popup (Center Image) | `PUBLIC_TEST_UNIT_ID_POPUP_CENTER` |
| Popup Video (Bottom 16:9) | `PUBLIC_TEST_UNIT_ID_POPUP_BOTTOM_VIDEO_16_9` |
| Popup Video (Bottom 9:16) | `PUBLIC_TEST_UNIT_ID_POPUP_BOTTOM_VIDEO_9_16` |
| Popup Video (Center 16:9) | `PUBLIC_TEST_UNIT_ID_POPUP_CENTER_VIDEO_16_9` |
| Popup Video (Center 9:16) | `PUBLIC_TEST_UNIT_ID_POPUP_CENTER_VIDEO_9_16` |
### Test Ad Usage Example
```typescript theme={null}
// Separate development/production environments
const POPUP_UNIT_ID = __DEV__
? 'PUBLIC_TEST_UNIT_ID_POPUP_BOTTOM' // Test unit ID
: 'YOUR_PRODUCTION_UNIT_ID'; // Production unit ID
const popupAd = new AdropPopupAd(POPUP_UNIT_ID);
```
Make sure to use the actual unit ID created in the Ad Control console for production releases.
***
## Error Handling
Handle errors appropriately by checking error codes on ad load or display failure.
```typescript theme={null}
const listener: AdropListener = {
onAdFailedToReceive: (ad: AdropPopupAd, errorCode?: any) => {
console.error('Ad reception failed:', errorCode);
switch (errorCode) {
case AdropErrorCode.network:
// Handle network error
console.log('Please check your network connection');
break;
case AdropErrorCode.adNoFill:
// Insufficient ad inventory
console.log('No ads available to display');
break;
case AdropErrorCode.invalidUnit:
// Invalid unit ID
console.log('Please check your ad unit ID');
break;
default:
console.log('Unable to load ad');
}
},
onAdFailedToShowFullScreen: (ad: AdropPopupAd, errorCode?: any) => {
console.error('Ad display failed:', errorCode);
// Retry later
setTimeout(() => {
ad.load();
}, 30000); // Retry after 30 seconds
},
};
```
***
## Best Practices
### 1. Appropriate Display Timing
Popup ads are most effective at these times:
```typescript theme={null}
// Good: App launch
useEffect(() => {
const ad = new AdropPopupAd('YOUR_UNIT_ID');
ad.listener = {
onAdReceived: (ad) => ad.show(),
onAdFailedToReceive: (_, error) => console.log(error),
};
ad.load();
return () => ad.destroy();
}, []);
// Good: Content load completion
const onContentLoaded = () => {
if (shouldShowPopupAd()) {
popupAd?.show();
}
};
// Good: Specific event completion
const onAchievementUnlocked = () => {
popupAd?.show();
};
// Bad: While user is working
const onUserTyping = () => {
popupAd?.show(); // Harms user experience
};
```
### 2. Frequency Limiting
Don't show popup ads too frequently.
```typescript theme={null}
import AsyncStorage from '@react-native-async-storage/async-storage';
const LAST_SHOWN_KEY = 'popup_ad_last_shown';
const MINIMUM_INTERVAL = 3600000; // 1 hour (milliseconds)
// Check if ad can be shown
const canShowAd = async (): Promise => {
try {
const lastShown = await AsyncStorage.getItem(LAST_SHOWN_KEY);
if (!lastShown) return true;
const currentTime = Date.now();
const lastShownTime = parseInt(lastShown, 10);
return currentTime - lastShownTime >= MINIMUM_INTERVAL;
} catch (error) {
return true;
}
};
// Record ad shown
const recordAdShown = async () => {
try {
await AsyncStorage.setItem(LAST_SHOWN_KEY, Date.now().toString());
} catch (error) {
console.error('Error recording ad shown:', error);
}
};
// Usage
const showAdIfAllowed = async () => {
if (await canShowAd()) {
popupAd?.show();
await recordAdShown();
}
};
```
### 3. Preload Ads
Preload ads for better user experience.
```typescript theme={null}
const [isAdReady, setIsAdReady] = useState(false);
useEffect(() => {
// Preload at app launch
const ad = new AdropPopupAd('YOUR_UNIT_ID');
ad.listener = {
onAdReceived: () => setIsAdReady(true),
onAdFailedToReceive: (_, error) => console.log(error),
};
ad.load();
setPopupAd(ad);
return () => ad.destroy();
}, []);
// Show immediately at desired time
const showPopupNow = () => {
if (isAdReady) {
popupAd?.show();
}
};
```
### 4. Memory Management
Always clean up ads on component unmount.
```typescript theme={null}
useEffect(() => {
const ad = new AdropPopupAd('YOUR_UNIT_ID');
setPopupAd(ad);
// Cleanup function
return () => {
ad.destroy();
};
}, []);
```
### 5. Close Popup on Click
Close popup on ad click for better user experience.
```typescript theme={null}
const listener: AdropListener = {
onAdClicked: (ad: AdropPopupAd) => {
console.log('Ad clicked:', ad.destinationURL);
// Auto-close popup on click
ad.close();
},
};
```
***
## Troubleshooting
### Ad Not Displaying
* Verify SDK is initialized
* Check if unit ID is correct
* Check network connection
* Use test unit ID in test environment
* Check error code to identify cause
* Retry later if ad inventory is insufficient
* Verify native SDK is properly integrated
* Verify `show()` is called after `onAdReceived` callback
* Check `isLoaded` property to confirm ad is loaded
* Verify native module is properly connected
### Memory Leak
```typescript theme={null}
// Good: Using cleanup function
useEffect(() => {
const ad = new AdropPopupAd('YOUR_UNIT_ID');
setPopupAd(ad);
return () => {
ad.destroy(); // Must call
};
}, []);
// Bad: Not cleaning up
useEffect(() => {
const ad = new AdropPopupAd('YOUR_UNIT_ID');
setPopupAd(ad);
// No destroy() call - memory leak!
}, []);
```
***
## Related Documentation
SDK installation and initialization
Implementing interstitial ads
Implementing rewarded ads
Implementing banner ads
# Reference
Source: https://docs.adrop.io/sdk/react-native/reference
Complete API reference for the React Native SDK.
## Adrop
Class for initializing the Adrop SDK and managing global settings.
### initialize
```typescript theme={null}
static initialize(
production: boolean,
targetCountries?: string[],
useInAppBrowser?: boolean
): void
```
Initializes the Adrop SDK.
**Parameters:**
* `production` (boolean): Production mode (true: production, false: development)
* `targetCountries` (string\[], optional): Target country code array (default: \[])
* `useInAppBrowser` (boolean, optional): Whether to use in-app browser (default: false)
**Example:**
```typescript theme={null}
import { Adrop } from 'adrop-ads-react-native'
Adrop.initialize(true, ['KR', 'US'], false)
```
### setUID
```typescript theme={null}
static setUID(uid: string): void
```
Sets the unique user identifier.
**Parameters:**
* `uid` (string): Unique user identifier
**Example:**
```typescript theme={null}
Adrop.setUID('user123')
```
### setTheme
```typescript theme={null}
static setTheme(theme: AdropTheme): void
```
Sets the app theme.
**Parameters:**
* `theme` (AdropTheme): Theme value ('light', 'dark', 'auto')
**Example:**
```typescript theme={null}
import { Adrop, AdropTheme } from 'adrop-ads-react-native'
Adrop.setTheme(AdropTheme.dark)
```
### setMarketingConsent
```typescript theme={null}
static setMarketingConsent(consent: boolean): void
```
Sets the user's marketing consent for push notification ad targeting. Call this after `initialize()`.
**Parameters:**
* `consent` (boolean): `true` for opt-in, `false` for opt-out
**Example:**
```typescript theme={null}
import { Adrop } from 'adrop-ads-react-native'
Adrop.setMarketingConsent(true)
```
### registerWebView
```typescript theme={null}
static registerWebView(viewTag: number): Promise
```
Registers a native WebView for backfill ad support. Call this after the WebView is mounted.
**Parameters:**
* `viewTag` (number): Native view tag obtained via `findNodeHandle()` from the `View` wrapping the WebView
**Example:**
```typescript theme={null}
import { useEffect, useRef } from 'react'
import { findNodeHandle, View } from 'react-native'
import { Adrop } from 'adrop-ads-react-native'
const containerRef = useRef(null)
useEffect(() => {
if (containerRef.current) {
const tag = findNodeHandle(containerRef.current)
if (tag != null) {
Adrop.registerWebView(tag)
}
}
}, [])
```
Requires `adrop-ads-backfill` module. If not installed, this call is silently ignored.
***
## AdropTheme
Constant object for specifying app theme.
```typescript theme={null}
const AdropTheme = {
light: 'light',
dark: 'dark',
auto: 'auto',
} as const
type AdropTheme = (typeof AdropTheme)[keyof typeof AdropTheme]
```
**Values:**
* `light`: Light theme
* `dark`: Dark theme
* `auto`: Follow system setting
***
## BrowserTarget
Enum for specifying how ad destination URLs should be opened.
```typescript theme={null}
enum BrowserTarget {
EXTERNAL = 0,
INTERNAL = 1,
}
```
**Values:**
* `EXTERNAL`: Open URL in external browser (default)
* `INTERNAL`: Open URL in in-app browser
***
## AdropErrorCode
Error codes that may occur during ad loading and display.
```typescript theme={null}
enum AdropErrorCode {
network = 'ERROR_CODE_NETWORK',
internal = 'ERROR_CODE_INTERNAL',
initialize = 'ERROR_CODE_INITIALIZE',
invalidUnit = 'ERROR_CODE_INVALID_UNIT',
notTargetCountry = 'ERROR_CODE_NOT_TARGET_COUNTRY',
inactive = 'ERROR_CODE_AD_INACTIVE',
adNoFill = 'ERROR_CODE_AD_NO_FILL',
adDuplicated = 'ERROR_CODE_AD_LOAD_DUPLICATED',
adLoading = 'ERROR_CODE_AD_LOADING',
adEmpty = 'ERROR_CODE_AD_EMPTY',
adShown = 'ERROR_CODE_AD_SHOWN',
adHideForToday = 'ERROR_CODE_AD_HIDE_FOR_TODAY',
adLandscapeUnsupported = 'ERROR_CODE_LANDSCAPE_UNSUPPORTED',
backfillNoFill = 'ERROR_CODE_AD_BACKFILL_NO_FILL',
undefined = 'UNDEFINED',
}
```
**Error Codes:**
* `network`: Network error
* `internal`: Internal error
* `initialize`: SDK initialization error
* `invalidUnit`: Invalid ad unit
* `notTargetCountry`: Not a target country
* `inactive`: Deactivated ad
* `adNoFill`: No ad available
* `adDuplicated`: Duplicate ad load request
* `adLoading`: Ad is loading
* `adEmpty`: Ad is empty
* `adShown`: Ad already shown
* `adHideForToday`: Ad hidden for today
* `adLandscapeUnsupported`: Landscape mode not supported
* `backfillNoFill`: Backfill ad not available
* `undefined`: Undefined error
***
## AdropAdChoicesPosition
Enum for setting the AdChoices badge position on backfill native ads.
### Definition
```typescript theme={null}
enum AdropAdChoicesPosition {
topLeft = 0,
topRight = 1,
bottomLeft = 2,
bottomRight = 3,
}
```
### Values
| Value | Integer | Description |
| - | - | - |
| `topLeft` | 0 | Top-left corner |
| `topRight` | 1 | Top-right corner (default) |
| `bottomLeft` | 2 | Bottom-left corner |
| `bottomRight` | 3 | Bottom-right corner |
### Usage Example
```typescript theme={null}
import { AdropNativeAd, AdropAdChoicesPosition } from 'adrop-ads-react-native'
const nativeAd = new AdropNativeAd(
'YOUR_UNIT_ID',
false,
AdropAdChoicesPosition.bottomRight
)
```
***
## AdropBanner
React component for displaying banner ads.
### Props
```typescript theme={null}
type AdropBannerProp = {
style: { height: number; width: number | string }
unitId: string
useCustomClick?: boolean
adSize?: { width: number; height: number } | null
autoLoad?: boolean
onAdReceived?: (unitId: string, metadata?: AdropBannerMetadata) => void
onAdImpression?: (unitId: string, metadata?: AdropBannerMetadata) => void
onAdClicked?: (unitId: string, metadata?: AdropBannerMetadata) => void
onAdFailedToReceive?: (unitId: string, errorCode?: any) => void
onAdVideoStart?: (unitId: string) => void
onAdVideoEnd?: (unitId: string) => void
}
```
**Props:**
* `style` (object, required): Banner style (including height and width)
* `unitId` (string, required): Ad unit ID
* `useCustomClick` (boolean, optional): Custom click handling (default: false)
* `adSize` (object, optional): Ad size setting
* `autoLoad` (boolean, optional): Auto load (default: true)
* `onAdReceived` (function, optional): Called on ad reception
* `onAdImpression` (function, optional): Called on ad impression
* `onAdClicked` (function, optional): Called on ad click
* `onAdFailedToReceive` (function, optional): Called on ad reception failure
* `onAdVideoStart` (function, optional): Called when a video ad starts playing
* `onAdVideoEnd` (function, optional): Called when a video ad finishes playing
### Methods
**load()**
```typescript theme={null}
load(): void
```
Loads the ad. (Called via ref)
### AdropBannerMetadata
```typescript theme={null}
type AdropBannerMetadata = {
creativeId: string
txId: string
campaignId: string
destinationURL: string
browserTarget: BrowserTarget
}
```
**Example:**
```typescript theme={null}
import { AdropBanner } from 'adrop-ads-react-native'
console.log('Ad received', metadata)}
onAdFailedToReceive={(unitId, errorCode) => console.log('Ad failed', errorCode)}
/>
```
***
## AdropNativeAd
Class for managing native ads.
### Constructor
```typescript theme={null}
new AdropNativeAd(
unitId: string,
useCustomClick?: boolean,
preferredAdChoicesPosition?: AdropAdChoicesPosition
)
```
**Parameters:**
| Parameter | Type | Description |
| - | - | - |
| `unitId` | `string` | Ad unit ID |
| `useCustomClick` | `boolean` | Custom click handling (default: `false`) |
| `preferredAdChoicesPosition` | `AdropAdChoicesPosition` | AdChoices badge position for backfill native ads (default: `AdropAdChoicesPosition.topRight`) |
### Properties
**isLoaded** (readonly)
```typescript theme={null}
get isLoaded(): boolean
```
Returns whether the ad is loaded.
**unitId** (readonly)
```typescript theme={null}
get unitId(): string
```
Returns the ad unit ID.
**requestId** (readonly, deprecated)
```typescript theme={null}
get requestId(): string // deprecated - always returns ''
```
*Deprecated.* This property always returns an empty string and will be removed in future versions.
**creativeId** (readonly)
```typescript theme={null}
get creativeId(): string
```
Returns the creative ID.
**txId** (readonly)
```typescript theme={null}
get txId(): string
```
Returns the transaction ID.
**campaignId** (readonly)
```typescript theme={null}
get campaignId(): string
```
Returns the campaign ID.
**useCustomClick** (readonly)
```typescript theme={null}
get useCustomClick(): boolean
```
Returns whether custom click handling is enabled.
**isBackfilled** (readonly)
```typescript theme={null}
get isBackfilled(): boolean
```
Returns whether it's a backfill ad.
**creativeType** (readonly)
```typescript theme={null}
get creativeType(): 'display' | 'video'
```
Returns the loaded creative type. Returns `'display'` until an ad response is received.
**isVideoAd** (readonly)
```typescript theme={null}
get isVideoAd(): boolean
```
Returns whether the ad is a video ad.
**browserTarget** (readonly)
```typescript theme={null}
get browserTarget(): BrowserTarget
```
Returns the browser target setting.
**properties** (readonly)
```typescript theme={null}
get properties(): AdropNativeProperties
```
Returns ad properties.
**listener**
```typescript theme={null}
listener?: AdropNativeAdListener
```
Sets the ad event listener.
### Methods
**load()**
```typescript theme={null}
load(): void
```
Loads the ad.
**destroy()**
```typescript theme={null}
destroy(): void
```
Releases the ad instance.
### AdropNativeProperties
```typescript theme={null}
type AdropNativeProperties = {
icon?: string
cover?: string
headline?: string
body?: string
creative?: string
asset?: string
destinationURL?: string
advertiserURL?: string
accountTag?: string
creativeTag?: string
advertiser?: string
callToAction?: string
profile?: AdropNativeProfile
extra?: Record
isBackfilled?: boolean
}
```
### AdropNativeProfile
```typescript theme={null}
type AdropNativeProfile = {
displayName: string
displayLogo: string
}
```
### AdropNativeAdListener
```typescript theme={null}
interface AdropNativeAdListener {
onAdReceived?: (ad: AdropNativeAd) => void
onAdClicked?: (ad: AdropNativeAd) => void
onAdImpression?: (ad: AdropNativeAd) => void
onAdFailedToReceive?: (ad: AdropNativeAd, errorCode?: any) => void
onAdVideoStart?: (ad: AdropNativeAd) => void
onAdVideoEnd?: (ad: AdropNativeAd) => void
}
```
***
## AdropNativeAdView
Container component for displaying native ads.
### Props
```typescript theme={null}
type Props = ViewProps & {
nativeAd?: AdropNativeAd
}
```
**Props:**
* `nativeAd` (AdropNativeAd, optional): Native ad instance
* ...ViewProps: All React Native View props
**Example:**
```typescript theme={null}
import { AdropNativeAdView } from 'adrop-ads-react-native'
{/* Native ad views */}
```
***
## Native Ad View Components
Components for displaying each element of a native ad. All components must be used inside `AdropNativeAdView`.
### AdropIconView
Displays the ad icon.
```typescript theme={null}
interface IconViewProps extends Omit {
source?: ImageSourcePropType | undefined
}
```
**Example:**
```typescript theme={null}
import { AdropIconView } from 'adrop-ads-react-native'
```
### AdropHeadLineView
Displays the ad headline.
```typescript theme={null}
const AdropHeadLineView: React.FC
```
**Example:**
```typescript theme={null}
import { AdropHeadLineView } from 'adrop-ads-react-native'
```
### AdropBodyView
Displays the ad body text.
```typescript theme={null}
const AdropBodyView: React.FC
```
**Example:**
```typescript theme={null}
import { AdropBodyView } from 'adrop-ads-react-native'
```
### AdropMediaView
Displays the ad media (image/video).
```typescript theme={null}
const AdropMediaView: React.FC
```
**Example:**
```typescript theme={null}
import { AdropMediaView } from 'adrop-ads-react-native'
```
### AdropCallToActionView
Displays the ad CTA (Call To Action) button.
```typescript theme={null}
const AdropCallToActionView: React.FC
```
**Example:**
```typescript theme={null}
import { AdropCallToActionView } from 'adrop-ads-react-native'
```
### AdropAdvertiserView
Displays the advertiser information.
```typescript theme={null}
const AdropAdvertiserView: React.FC
```
**Example:**
```typescript theme={null}
import { AdropAdvertiserView } from 'adrop-ads-react-native'
```
### AdropProfileLogoView
Displays the profile logo.
```typescript theme={null}
interface IconViewProps extends Omit {
source?: ImageSourcePropType | undefined
}
```
**Example:**
```typescript theme={null}
import { AdropProfileLogoView } from 'adrop-ads-react-native'
```
### AdropProfileNameView
Displays the profile name.
```typescript theme={null}
const AdropProfileNameView: React.FC
```
**Example:**
```typescript theme={null}
import { AdropProfileNameView } from 'adrop-ads-react-native'
```
***
## AdropInterstitialAd
Class for managing interstitial ads.
### Constructor
```typescript theme={null}
constructor(unitId: string)
```
**Parameters:**
* `unitId` (string): Ad unit ID
### Properties
**isLoaded** (readonly)
```typescript theme={null}
get isLoaded(): boolean
```
Returns whether the ad is loaded.
**unitId** (readonly)
```typescript theme={null}
get unitId(): string
```
Returns the ad unit ID.
**creativeId** (readonly)
```typescript theme={null}
get creativeId(): string
```
Returns the creative ID.
**txId** (readonly)
```typescript theme={null}
get txId(): string
```
Returns the transaction ID.
**campaignId** (readonly)
```typescript theme={null}
get campaignId(): string
```
Returns the campaign ID.
**creativeType** (readonly)
```typescript theme={null}
get creativeType(): 'display' | 'video'
```
Returns the loaded creative type. Returns `'display'` until an ad response is received.
**destinationURL** (readonly)
```typescript theme={null}
get destinationURL(): string
```
Returns the destination URL.
**browserTarget** (readonly)
```typescript theme={null}
get browserTarget(): BrowserTarget
```
Returns the browser target setting.
**listener**
```typescript theme={null}
listener?: AdropListener
```
Sets the ad event listener.
### Methods
**load()**
```typescript theme={null}
load(): void
```
Loads the ad.
**show()**
```typescript theme={null}
show(): void
```
Shows the ad.
**close()**
```typescript theme={null}
close(): void
```
Closes the ad programmatically.
**destroy()**
```typescript theme={null}
destroy(): void
```
Releases the ad instance.
**Example:**
```typescript theme={null}
import { AdropInterstitialAd } from 'adrop-ads-react-native'
const interstitialAd = new AdropInterstitialAd('YOUR_UNIT_ID')
interstitialAd.listener = {
onAdReceived: (ad) => {
console.log('Ad loaded')
ad.show()
},
onAdFailedToReceive: (ad, errorCode) => {
console.log('Ad failed to load', errorCode)
},
}
interstitialAd.load()
```
***
## AdropRewardedAd
Class for managing rewarded ads.
### Constructor
```typescript theme={null}
constructor(unitId: string)
```
**Parameters:**
* `unitId` (string): Ad unit ID
### Properties
Same properties as AdropInterstitialAd.
### Methods
**load()**
```typescript theme={null}
load(): void
```
Loads the ad.
**show()**
```typescript theme={null}
show(): void
```
Shows the ad.
**close()**
```typescript theme={null}
close(): void
```
Closes the ad programmatically.
**destroy()**
```typescript theme={null}
destroy(): void
```
Releases the ad instance.
**setServerSideVerificationOptions()**
```typescript theme={null}
setServerSideVerificationOptions(options: ServerSideVerificationOptions): void
```
Sets options for server-side reward verification. Call before `load()`.
* `options.userId` (string, optional): User identifier sent in the server callback
* `options.customData` (string, optional): Custom data sent in the server callback
**Example:**
```typescript theme={null}
import { AdropRewardedAd } from 'adrop-ads-react-native'
const rewardedAd = new AdropRewardedAd('YOUR_UNIT_ID')
// Set server-side verification options
rewardedAd.setServerSideVerificationOptions({
userId: 'user_12345',
customData: 'extra_info',
})
rewardedAd.listener = {
onAdReceived: (ad) => {
console.log('Ad loaded')
ad.show()
},
onAdEarnRewardHandler: (ad, type, amount) => {
console.log(`Reward earned: type=${type}, amount=${amount}`)
},
onAdFailedToReceive: (ad, errorCode) => {
console.log('Ad failed to load', errorCode)
},
}
rewardedAd.load()
```
***
## AdropPopupAd
Class for managing popup ads.
### Constructor
```typescript theme={null}
constructor(
unitId: string,
colors?: AdropPopupAdColors,
useCustomClick?: boolean
)
```
**Parameters:**
* `unitId` (string): Ad unit ID
* `colors` (AdropPopupAdColors, optional): Popup color customization
* `useCustomClick` (boolean, optional): Custom click handling (default: false)
### Properties
**isLoaded** (readonly)
```typescript theme={null}
get isLoaded(): boolean
```
Returns whether the ad is loaded.
**unitId** (readonly)
```typescript theme={null}
get unitId(): string
```
Returns the ad unit ID.
**creativeId** (readonly)
```typescript theme={null}
get creativeId(): string
```
Returns the creative ID.
**txId** (readonly)
```typescript theme={null}
get txId(): string
```
Returns the transaction ID.
**campaignId** (readonly)
```typescript theme={null}
get campaignId(): string
```
Returns the campaign ID.
**creativeType** (readonly)
```typescript theme={null}
get creativeType(): 'display' | 'video'
```
Returns the loaded creative type. Returns `'display'` until an ad response is received.
**destinationURL** (readonly)
```typescript theme={null}
get destinationURL(): string
```
Returns the destination URL.
**browserTarget** (readonly)
```typescript theme={null}
get browserTarget(): BrowserTarget
```
Returns the browser target setting.
**listener**
```typescript theme={null}
listener?: AdropListener
```
Sets the ad event listener.
### Methods
**load()**
```typescript theme={null}
load(): void
```
Loads the ad.
**show()**
```typescript theme={null}
show(): void
```
Shows the ad.
**close()**
```typescript theme={null}
close(): void
```
Closes the popup ad.
**destroy()**
```typescript theme={null}
destroy(): void
```
Releases the ad instance.
### AdropPopupAdColors
```typescript theme={null}
type AdropPopupAdColors = {
closeTextColor?: string
hideForTodayTextColor?: string
backgroundColor?: string
}
```
**Properties:**
* `closeTextColor` (string, optional): Close button text color
* `hideForTodayTextColor` (string, optional): 'Hide for today' text color
* `backgroundColor` (string, optional): Background color
***
## AdropListener
Listener interface for receiving ad events.
```typescript theme={null}
type AdropListener = {
onAdReceived?: (ad: AdropAd) => void
onAdClicked?: (ad: AdropAd) => void
onAdImpression?: (ad: AdropAd) => void
onAdFailedToReceive?: (ad: AdropAd, errorCode?: any) => void
onAdDidPresentFullScreen?: (ad: AdropAd) => void
onAdWillPresentFullScreen?: (ad: AdropAd) => void
onAdDidDismissFullScreen?: (ad: AdropAd) => void
onAdWillDismissFullScreen?: (ad: AdropAd) => void
onAdFailedToShowFullScreen?: (ad: AdropAd, errorCode?: any) => void
onAdEarnRewardHandler?: (ad: AdropAd, type: number, amount: number) => void
onAdBackButtonPressed?: (ad: AdropAd) => void
onAdVideoStart?: (ad: AdropAd) => void
onAdVideoEnd?: (ad: AdropAd) => void
}
```
**Events:**
* `onAdReceived`: Ad reception complete
* `onAdClicked`: Ad clicked
* `onAdImpression`: Ad impression
* `onAdFailedToReceive`: Ad reception failed
* `onAdDidPresentFullScreen`: Full-screen ad presented
* `onAdWillPresentFullScreen`: Full-screen ad about to present
* `onAdDidDismissFullScreen`: Full-screen ad dismissed
* `onAdWillDismissFullScreen`: Full-screen ad about to dismiss
* `onAdFailedToShowFullScreen`: Full-screen ad failed to show
* `onAdEarnRewardHandler`: Reward earned (rewarded ads only)
* `onAdBackButtonPressed`: Back button pressed (Android only)
* `onAdVideoStart`: Video ad started playing
* `onAdVideoEnd`: Video ad finished playing
***
## useAdropInterstitialAd
Manages interstitial ads with a React Hook.
### Signature
```typescript theme={null}
function useAdropInterstitialAd(unitId: string | null): AdHookReturns & AdStates
```
**Parameters:**
* `unitId` (string | null): Ad unit ID
**Returns:**
* `load`: Ad load function
* `show`: Ad show function
* `reset`: State reset function
* `isBackPressed`: Back button pressed status (Android only)
* `isClicked`: Click status
* `isClosed`: Closed status
* `isEarnRewarded`: Whether the user has earned a reward
* `isLoaded`: Loaded status
* `isOpened`: Opened status
* `isReady`: Ready status
* `errorCode`: Error code
* `reward`: Reward info (`{ type: number, amount: number }` or `undefined`)
* `browserTarget`: Browser target setting (`BrowserTarget` or `undefined`)
**Example:**
```typescript theme={null}
import { useAdropInterstitialAd } from 'adrop-ads-react-native'
function MyComponent() {
const { load, show, isLoaded, errorCode } = useAdropInterstitialAd('YOUR_UNIT_ID')
useEffect(() => {
load()
}, [])
useEffect(() => {
if (isLoaded) {
show()
}
}, [isLoaded])
return
}
```
***
## useAdropRewardedAd
Manages rewarded ads with a React Hook.
### Signature
```typescript theme={null}
function useAdropRewardedAd(unitId: string | null): AdHookReturns & AdStates
```
**Parameters:**
* `unitId` (string | null): Ad unit ID
**Returns:**
* `load`: Ad load function
* `show`: Ad show function
* `reset`: State reset function
* `isBackPressed`: Back button pressed status (Android only)
* `isClicked`: Click status
* `isClosed`: Closed status
* `isEarnRewarded`: Whether the user has earned a reward
* `isLoaded`: Loaded status
* `isOpened`: Opened status
* `isReady`: Ready status
* `errorCode`: Error code
* `reward`: Reward info (`{ type: number, amount: number }` or `undefined`)
* `browserTarget`: Browser target setting (`BrowserTarget` or `undefined`)
**Example:**
```typescript theme={null}
import { useAdropRewardedAd } from 'adrop-ads-react-native'
function MyComponent() {
const { load, show, isLoaded, isEarnRewarded, reward } = useAdropRewardedAd('YOUR_UNIT_ID')
useEffect(() => {
load()
}, [])
useEffect(() => {
if (isLoaded) {
show()
}
}, [isLoaded])
useEffect(() => {
if (isEarnRewarded && reward) {
console.log(`Reward: type=${reward.type}, amount=${reward.amount}`)
}
}, [isEarnRewarded, reward])
return
}
```
***
## useAdropWebView
Hook for registering a WebView with the Adrop SDK. Handles view tag resolution and registration automatically.
### Signature
```typescript theme={null}
function useAdropWebView(): {
containerRef: React.RefObject
isReady: boolean
onLayout: () => void
}
```
**Returns:**
* `containerRef`: Ref to attach to the `View` wrapping the WebView
* `isReady`: Whether the WebView has been successfully registered
* `onLayout`: Callback to trigger registration on layout
**Example:**
```typescript theme={null}
import { View } from 'react-native'
import { WebView } from 'react-native-webview'
import { useAdropWebView } from 'adrop-ads-react-native'
function WebViewScreen() {
const { containerRef, isReady, onLayout } = useAdropWebView()
return (
)
}
```
***
## AdropMetrics
Class for managing user attributes and events.
### setProperty
```typescript theme={null}
static setProperty(key: string, value: any): void
```
Sets a user property.
**Parameters:**
* `key` (string): Property key
* `value` (any): Property value
**Example:**
```typescript theme={null}
import { AdropMetrics, AdropProperties, AdropGender } from 'adrop-ads-react-native'
AdropMetrics.setProperty(AdropProperties.AGE, 25)
AdropMetrics.setProperty(AdropProperties.GENDER, AdropGender.MALE)
AdropMetrics.setProperty(AdropProperties.BIRTH, '1998-01-01')
```
### sendEvent
```typescript theme={null}
static sendEvent(name: string, params?: Record): void
```
Sends a user behavior event. Supports nested `items` arrays for multi-item events.
**Parameters:**
* `name` (string): Event name (1–64 characters)
* `params` (Record\, optional): Event parameters
**Example:**
```typescript theme={null}
import { AdropMetrics } from 'adrop-ads-react-native'
// Simple event
AdropMetrics.sendEvent('app_open')
// Event with parameters
AdropMetrics.sendEvent('view_item', {
item_id: 'SKU-123',
item_name: 'Widget',
price: 29900,
})
// Multi-item event
AdropMetrics.sendEvent('purchase', {
tx_id: 'TXN-001',
currency: 'KRW',
items: [
{ item_id: 'SKU-001', item_name: 'Product A', price: 29900, quantity: 1 },
{ item_id: 'SKU-002', item_name: 'Product B', price: 15000, quantity: 2 },
],
})
```
### logEvent (deprecated)
```typescript theme={null}
/** @deprecated Use sendEvent instead */
static logEvent(name: string, params?: Record): void
```
Logs a custom event. Internally calls `sendEvent`.
**Parameters:**
* `name` (string): Event name
* `params` (object, optional): Event parameters
### properties
```typescript theme={null}
static properties(): Promise>
```
Gets all set properties.
**Returns:**
* Promise\>: Property object
***
## AdropProperties
Enum defining user property keys.
```typescript theme={null}
enum AdropProperties {
AGE = 'AGE',
BIRTH = 'BIRTH',
GENDER = 'GDR',
}
```
**Values:**
* `AGE`: Age
* `BIRTH`: Birth date
* `GENDER`: Gender
***
## AdropGender
Enum defining gender.
```typescript theme={null}
enum AdropGender {
MALE = 'M',
FEMALE = 'F',
OTHER = 'O',
UNKNOWN = 'U',
}
```
**Values:**
* `MALE`: Male
* `FEMALE`: Female
* `OTHER`: Other
* `UNKNOWN`: Unknown
***
## AdropConsent
Class for managing user consent (GDPR, CCPA, etc.).
### requestConsentInfoUpdate
```typescript theme={null}
static requestConsentInfoUpdate(): Promise
```
Updates consent information and shows the consent popup if needed.
**Returns:**
* `Promise`: Consent result
**Example:**
```typescript theme={null}
import { AdropConsent } from 'adrop-ads-react-native'
const result = await AdropConsent.requestConsentInfoUpdate()
console.log('Status:', result.status)
console.log('Can request ads:', result.canRequestAds)
console.log('Can show personalized ads:', result.canShowPersonalizedAds)
```
### getConsentStatus
```typescript theme={null}
static getConsentStatus(): Promise
```
Gets the current consent status.
**Returns:**
* `Promise`: Current consent status
**Example:**
```typescript theme={null}
const status = await AdropConsent.getConsentStatus()
```
### canRequestAds
```typescript theme={null}
static canRequestAds(): Promise
```
Checks whether ads can be requested based on current consent.
**Returns:**
* `Promise`: Whether ads can be requested
**Example:**
```typescript theme={null}
const canRequest = await AdropConsent.canRequestAds()
if (canRequest) {
// Load ads
}
```
### getPrivacyOptionsRequirementStatus
```typescript theme={null}
static getPrivacyOptionsRequirementStatus(): Promise
```
Returns whether a privacy options entry point must be shown. Meaningful after `requestConsentInfoUpdate()` completes in the current app session. Resolves `UNKNOWN` instead of rejecting when the consent manager is not available.
**Returns:**
* `Promise`: Whether an entry point is required
**Example:**
```typescript theme={null}
import {
AdropConsent,
AdropPrivacyOptionsRequirementStatus,
} from 'adrop-ads-react-native'
const status = await AdropConsent.getPrivacyOptionsRequirementStatus()
const required = status === AdropPrivacyOptionsRequirementStatus.REQUIRED
```
### showPrivacyOptionsForm
```typescript theme={null}
static showPrivacyOptionsForm(): Promise
```
Shows the privacy options form so users can change their consent. The form opens only while the requirement status is `REQUIRED`; otherwise the stored consent is left unchanged and the result carries the current state with an error.
**Returns:**
* `Promise`: Consent result after the form closes
**Example:**
```typescript theme={null}
const result = await AdropConsent.showPrivacyOptionsForm()
console.log('Personalized ads:', result.canShowPersonalizedAds)
```
### reset
```typescript theme={null}
static reset(): void
```
Resets consent settings. For testing and debugging purposes — do not call it in production code. To let users change their consent, use `showPrivacyOptionsForm()`.
**Example:**
```typescript theme={null}
AdropConsent.reset()
```
### setDebugSettings
```typescript theme={null}
static setDebugSettings(geography: AdropConsentDebugGeography): void
```
Sets debug geography for testing consent flows. Only works in DEBUG builds.
**Parameters:**
* `geography` (AdropConsentDebugGeography): Geography to simulate
**Example:**
```typescript theme={null}
import { AdropConsent, AdropConsentDebugGeography } from 'adrop-ads-react-native'
AdropConsent.setDebugSettings(AdropConsentDebugGeography.EEA)
```
***
## AdropConsentStatus
Enum representing user consent status.
```typescript theme={null}
enum AdropConsentStatus {
UNKNOWN = 0,
REQUIRED = 1,
NOT_REQUIRED = 2,
OBTAINED = 3,
}
```
**Values:**
* `UNKNOWN`: Not yet determined
* `REQUIRED`: Consent required (popup display needed)
* `NOT_REQUIRED`: Consent not required (not applicable region)
* `OBTAINED`: Consent obtained
***
## AdropPrivacyOptionsRequirementStatus
Enum representing whether a privacy options entry point must be shown.
```typescript theme={null}
enum AdropPrivacyOptionsRequirementStatus {
UNKNOWN = 0,
REQUIRED = 1,
NOT_REQUIRED = 2,
}
```
**Values:**
* `UNKNOWN`: Not determined yet (`requestConsentInfoUpdate()` has not completed in this app session, or the installed backfill does not support privacy options)
* `REQUIRED`: An entry point must be shown
* `NOT_REQUIRED`: No entry point is needed
***
## AdropConsentResult
Interface representing the result of a consent info update.
```typescript theme={null}
interface AdropConsentResult {
status: AdropConsentStatus
canRequestAds: boolean
canShowPersonalizedAds: boolean
error?: string
}
```
**Properties:**
* `status` (AdropConsentStatus): Consent status
* `canRequestAds` (boolean): Whether ads can be requested
* `canShowPersonalizedAds` (boolean): Whether personalized ads can be shown
* `error` (string, optional): Error message if any
***
## AdropConsentDebugGeography
Enum for specifying debug geography in consent testing.
```typescript theme={null}
enum AdropConsentDebugGeography {
DISABLED = 0,
EEA = 1,
NOT_EEA = 2, // deprecated - use OTHER instead
REGULATED_US_STATE = 3,
OTHER = 4,
}
```
**Values:**
* `DISABLED`: No debug setting (use actual location)
* `EEA`: European Economic Area (GDPR applies)
* `NOT_EEA`: *(deprecated - use `OTHER` instead)*
* `REGULATED_US_STATE`: Regulated US states (California, etc., CCPA applies)
* `OTHER`: Unregulated region
***
## Deprecated APIs
The following APIs are deprecated and will be removed in future versions. Migrate to the recommended alternatives.
### AdropAd.destinationUrl
`destinationUrl` is deprecated. Use `destinationURL` instead.
```typescript theme={null}
// Before (deprecated)
const url = ad.destinationUrl
// After
const url = ad.destinationURL
```
### AdropPopupAd.createIds()
`createIds()` is deprecated. Use `creativeId` instead.
```typescript theme={null}
// Before (deprecated)
const ids = popupAd.createIds()
// After
const id = popupAd.creativeId
```
### AdropNativeAd.requestId
`AdropNativeAd.requestId` is deprecated and always returns an empty string. This property will be removed in future versions.
```typescript theme={null}
// Before (deprecated)
const reqId = nativeAd.requestId // always returns ''
// After - remove usage of requestId
```
### AdropMetrics.logEvent()
`logEvent()` is deprecated. Use `sendEvent()` instead.
```typescript theme={null}
// Before (deprecated)
AdropMetrics.logEvent('purchase', { item_id: 'SKU-001' })
// After
AdropMetrics.sendEvent('purchase', { item_id: 'SKU-001' })
```
***
## Backfill Revenue (`onPaidEvent`)
Callback that delivers revenue information when a backfill ad is served. It fires only for backfill ads and is never invoked for Adrop direct ads.
### Supported ad formats
Supported on Banner, Native, Interstitial, and Rewarded. Not supported on Popup or Splash.
### Callback signature
`AdropBanner` is a functional component that takes `onPaidEvent` as a prop. Class-based formats attach it via `listener.onPaidEvent`. The first argument differs between the two.
```typescript theme={null}
// AdropBanner (component prop) — first argument is the unit id
onPaidEvent?: (unitId: string, value: AdropAdValue) => void
// AdropNativeAd | AdropInterstitialAd | AdropRewardedAd (listener property)
// first argument is the ad instance
onPaidEvent?: (ad, value: AdropAdValue) => void
```
### `AdropAdValue`
| Field | Type | Description |
| - | - | - |
| `network` | `string` | Backfill network identifier (e.g., `admob`) |
| `adSourceName` | `string \| undefined` | Mediation source inside the network (e.g., `AppLovin`) |
| `valueMicros` | `number` | Revenue in micro units (1/1,000,000 of `currencyCode`) |
| `currencyCode` | `string` | ISO 4217 currency code |
| `precision` | `'unknown' \| 'estimated' \| 'publisherProvided' \| 'precise'` | Precision |
### Usage
Banner:
```tsx theme={null}
{
analytics.track(unitId, value.valueMicros, value.currencyCode)
}}
/>
```
Native, Interstitial, Rewarded:
```typescript theme={null}
const ad = new AdropInterstitialAd('YOUR_UNIT_ID')
ad.listener = {
onPaidEvent: (ad, value) => {
analytics.track(ad.unitId, value.valueMicros, value.currencyCode)
},
}
ad.load()
```
Banner delivers `unitId` (string) as the first argument, while Native, Interstitial, and Rewarded deliver the ad instance — read `ad.unitId` from it.
# Rewarded Ads
Source: https://docs.adrop.io/sdk/react-native/rewarded
Learn how to implement rewarded ads in your React Native app.
## Overview
Rewarded ads are full-screen video ads that provide users with in-game currency, extra lives, hints, or other rewards when they watch the video to completion.
### Key Features
* Full-screen video ads
* Rewards only provided when user fully watches the ad
* User chooses to watch the ad (e.g., "Watch video to get a life" button)
* Configurable reward type and amount
Use test unit IDs in development environments.
***
## Implementation Methods
Rewarded ads can be implemented in two ways:
1. **Class Method** - Using `AdropRewardedAd` class directly
2. **Hook Method** - Using `useAdropRewardedAd` Hook (recommended)
***
## Hook Method Implementation (Recommended)
Use React Hooks for a more concise and React-like implementation.
### Basic Example
```tsx theme={null}
import React, { useCallback } from 'react';
import { Button, View, Text, StyleSheet } from 'react-native';
import { useAdropRewardedAd } from 'adrop-ads-react-native';
const RewardedAdScreen = () => {
const unitId = 'YOUR_UNIT_ID'; // or test unit ID
const {
load,
show,
reset,
isLoaded,
isOpened,
isClosed,
isEarnRewarded,
reward,
errorCode,
isReady
} = useAdropRewardedAd(unitId);
// Load ad on component mount
React.useEffect(() => {
if (isReady) {
load();
}
}, [isReady, load]);
// Load next ad after ad closes
React.useEffect(() => {
if (isClosed) {
console.log('Ad closed');
// Preload next ad
reset();
load();
}
}, [isClosed, reset, load]);
// Handle reward
React.useEffect(() => {
if (isEarnRewarded && reward) {
console.log(`Reward earned - Type: ${reward.type}, Amount: ${reward.amount}`);
grantReward(reward.type, reward.amount);
}
}, [isEarnRewarded, reward]);
const handleShowAd = useCallback(() => {
if (isLoaded) {
show();
} else {
console.log('Ad not loaded yet');
}
}, [isLoaded, show]);
const grantReward = (type: number, amount: number) => {
// Grant reward to user
console.log(`Granting reward: type=${type}, amount=${amount}`);
// Implement actual reward logic
};
return (
{errorCode && (
Error: {errorCode}
)}
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
gap: 16,
},
error: {
color: 'red',
},
});
export default RewardedAdScreen;
```
### useAdropRewardedAd Hook
The Hook automatically manages ad state.
#### Parameters
Ad unit ID from the Ad Control console. Pass `null` to skip ad instance creation.
#### Return Values
Requests an ad.
Shows the ad.
Resets ad state and creates a new ad instance.
Whether the ad is loaded and ready to show.
Whether the ad is displayed on screen.
Whether the ad has been closed.
Whether the user has earned a reward.
Reward information (type and amount).
Error code (only present on error).
Whether the ad instance is ready for use.
Whether the ad has been clicked.
Whether the back button was pressed while the ad is displayed. **Android only.**
Browser target setting of the loaded ad (`BrowserTarget.EXTERNAL` or `BrowserTarget.INTERNAL`).
***
## Class Method Implementation
You can implement ads using the `AdropRewardedAd` class directly.
### Basic Example
```tsx theme={null}
import React, { useEffect, useState, useCallback } from 'react';
import { Button, View, Text, StyleSheet } from 'react-native';
import { AdropRewardedAd, AdropListener } from 'adrop-ads-react-native';
const RewardedAdScreen = () => {
const [rewardedAd, setRewardedAd] = useState(null);
const [isLoaded, setIsLoaded] = useState(false);
useEffect(() => {
// 1. Create AdropRewardedAd instance
const ad = new AdropRewardedAd('YOUR_UNIT_ID');
// 2. Set up listener
const listener: AdropListener = {
onAdReceived: (ad) => {
console.log('Ad received:', ad.unitId);
setIsLoaded(true);
},
onAdFailedToReceive: (ad, errorCode) => {
console.log('Ad failed to receive:', errorCode);
setIsLoaded(false);
},
onAdDidPresentFullScreen: (ad) => {
console.log('Ad presented:', ad.unitId);
},
onAdDidDismissFullScreen: (ad) => {
console.log('Ad dismissed:', ad.unitId);
setIsLoaded(false);
// Preload next ad
ad.load();
},
onAdEarnRewardHandler: (ad, type, amount) => {
console.log(`Reward earned - Type: ${type}, Amount: ${amount}`);
grantReward(type, amount);
},
onAdClicked: (ad) => {
console.log('Ad clicked:', ad.unitId);
},
onAdImpression: (ad) => {
console.log('Ad impression:', ad.unitId);
},
onAdFailedToShowFullScreen: (ad, errorCode) => {
console.log('Ad failed to show:', errorCode);
},
};
ad.listener = listener;
setRewardedAd(ad);
// 3. Load ad
ad.load();
// Cleanup on unmount
return () => {
ad.destroy();
};
}, []);
const handleShowAd = useCallback(() => {
if (rewardedAd && isLoaded) {
rewardedAd.show();
} else {
console.log('Ad not loaded yet');
}
}, [rewardedAd, isLoaded]);
const grantReward = (type: number, amount: number) => {
// Grant reward to user
console.log(`Granting reward: type=${type}, amount=${amount}`);
// Implement actual reward logic
};
return (
rewardedAd?.load()}
/>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
gap: 16,
},
});
export default RewardedAdScreen;
```
### AdropRewardedAd Class
#### Constructor
```typescript theme={null}
new AdropRewardedAd(unitId: string)
```
Ad unit ID from the Ad Control console.
#### Properties
Ad unit ID (read-only).
Whether the ad is loaded and ready to show (read-only).
Creative ID of the loaded ad (read-only).
Transaction ID (read-only).
Campaign ID of the loaded ad (read-only).
Destination URL of the loaded ad (read-only).
Browser target setting of the loaded ad (`BrowserTarget.EXTERNAL` or `BrowserTarget.INTERNAL`) (read-only).
Listener for receiving ad events.
#### Methods
Requests an ad. Results are delivered via `onAdReceived` or `onAdFailedToReceive` listener callbacks.
Shows the ad.
Releases ad resources. Should be called on component unmount.
***
## AdropListener Interface
All callbacks are optional. Set only the ones you need.
### Callbacks
Called when ad is received successfully. You can call `show()` from this point.
Called when ad fails to receive. Check error code for failure reason.
Called when ad impression is recorded.
Called when user clicks the ad.
Called just before the ad screen is presented.
Called after the ad screen is fully presented.
Called just before the ad screen is dismissed.
Called after the ad screen is fully dismissed. Good time to preload next ad.
Called when ad fails to show. Check error code for failure reason.
Called when the user presses the back button while the ad is displayed. **Android only.**
### Reward-specific Callback
Called when user has watched the ad to completion and earned a reward.
* `type`: Reward type set in the Ad Control console (integer)
* `amount`: Reward amount set in the Ad Control console (integer)
***
## Reward Handling
### Hook Method Reward Handling
```tsx theme={null}
const RewardedAdScreen = () => {
const { isEarnRewarded, reward } = useAdropRewardedAd('YOUR_UNIT_ID');
useEffect(() => {
if (isEarnRewarded && reward) {
// Process reward
console.log(`Reward earned - Type: ${reward.type}, Amount: ${reward.amount}`);
grantReward(reward.type, reward.amount);
}
}, [isEarnRewarded, reward]);
const grantReward = (type: number, amount: number) => {
// Grant reward to user
// e.g., add coins, restore life, etc.
};
// ...
};
```
### Class Method Reward Handling
```tsx theme={null}
const listener: AdropListener = {
onAdEarnRewardHandler: (ad, type, amount) => {
console.log(`Reward earned - Type: ${type}, Amount: ${amount}`);
grantReward(type, amount);
},
// other callbacks...
};
const grantReward = (type: number, amount: number) => {
// Grant reward to user
// e.g., add coins, restore life, etc.
};
```
## Server-Side Verification
You can verify reward grants on your server to prevent fraud. Use `setServerSideVerificationOptions()` to pass user and custom data that will be included in server-side verification callbacks.
To receive SSV callbacks on your server, register a callback URL in the console: **\[Settings] > \[Integrations] > Reward Ad SSV**. The `userId` and `customData` set here are included in the encrypted payload sent to that URL. See the [Reward Ad SSV guide](/integrations/ssv) for payload decryption and setup.
### Setup
```tsx theme={null}
import { AdropRewardedAd } from 'adrop-ads-react-native'
const rewardedAd = new AdropRewardedAd('YOUR_UNIT_ID')
// Set server-side verification options before loading the ad
rewardedAd.setServerSideVerificationOptions({
userId: 'user_12345',
customData: 'extra_info',
})
rewardedAd.listener = {
onAdReceived: (ad) => {
ad.show()
},
onAdEarnRewardHandler: (ad, type, amount) => {
// Client-side callback (optional)
// Server-side verification callback will also be sent
console.log(`Reward earned: type=${type}, amount=${amount}`)
},
}
rewardedAd.load()
```
### ServerSideVerificationOptions
```typescript theme={null}
type ServerSideVerificationOptions = {
userId?: string
customData?: string
}
```
**Properties:**
* `userId` (string, optional): User identifier sent in the server callback
* `customData` (string, optional): Custom data sent in the server callback
When the ad falls back to backfill, `customData` is sanitized to base64url before being forwarded (`+`→`-`, `/`→`_`, `=` stripped; backfill 1.12.1+). If you pack a base64 payload into `customData`, decode it as base64url on your server.
`setServerSideVerificationOptions()` should be called before `load()`. The options are sent to the server when the user earns a reward.
***
## Testing
### Test Unit IDs
Always use test unit IDs during development. Testing with real unit IDs may result in invalid traffic and account suspension.
```tsx theme={null}
// Hook method
const unitId = __DEV__ ? 'PUBLIC_TEST_UNIT_ID_REWARDED' : 'YOUR_PRODUCTION_UNIT_ID';
const rewardedAd = useAdropRewardedAd(unitId);
// Class method
const unitId = __DEV__ ? 'PUBLIC_TEST_UNIT_ID_REWARDED' : 'YOUR_PRODUCTION_UNIT_ID';
const ad = new AdropRewardedAd(unitId);
```
***
## Error Handling
### Hook Method Error Handling
```tsx theme={null}
const RewardedAdScreen = () => {
const { load, errorCode, reset } = useAdropRewardedAd('YOUR_UNIT_ID');
useEffect(() => {
if (errorCode) {
console.log('Ad error:', errorCode);
// Handle by error type
if (errorCode === AdropErrorCode.network) {
// Network error: retry
setTimeout(() => {
reset();
load();
}, 3000);
}
}
}, [errorCode, reset, load]);
// ...
};
```
### Class Method Error Handling
```tsx theme={null}
const listener: AdropListener = {
onAdFailedToReceive: (ad, errorCode) => {
console.log('Ad receive failed:', errorCode);
if (errorCode === AdropErrorCode.network) {
// Network error: retry after 3 seconds
setTimeout(() => {
ad.load();
}, 3000);
}
},
onAdFailedToShowFullScreen: (ad, errorCode) => {
console.log('Ad show failed:', errorCode);
Alert.alert('Notice', 'Unable to show ad. Please try again later.');
},
// other callbacks...
};
```
***
## Best Practices
### 1. Preload Ads
Preload ads for better user experience.
```tsx theme={null}
// Hook method
const RewardedAdScreen = () => {
const { load, isClosed, reset, isReady } = useAdropRewardedAd('YOUR_UNIT_ID');
// Load ad on component mount
useEffect(() => {
if (isReady) {
load();
}
}, [isReady, load]);
// Preload next ad after ad closes
useEffect(() => {
if (isClosed) {
reset();
load();
}
}, [isClosed, reset, load]);
// ...
};
```
### 2. Check Ad Ready State
Check if ad is loaded and update UI accordingly.
```tsx theme={null}
const RewardedAdScreen = () => {
const { isLoaded, show } = useAdropRewardedAd('YOUR_UNIT_ID');
return (
);
};
```
### 3. Memory Management
Clean up ad resources on component unmount.
```tsx theme={null}
// Hook method automatically cleans up
const { ... } = useAdropRewardedAd('YOUR_UNIT_ID');
// Class method requires manual cleanup
useEffect(() => {
const ad = new AdropRewardedAd('YOUR_UNIT_ID');
// ...
return () => {
ad.destroy();
};
}, []);
```
### 4. Error Handling
Improve user experience with proper error handling.
```tsx theme={null}
const RewardedAdScreen = () => {
const { errorCode, reset, load } = useAdropRewardedAd('YOUR_UNIT_ID');
useEffect(() => {
if (errorCode) {
// Notify user of error
Alert.alert('Notice', 'Unable to load ad.');
// Retry on network error
if (errorCode === AdropErrorCode.network) {
setTimeout(() => {
reset();
load();
}, 3000);
}
}
}, [errorCode, reset, load]);
// ...
};
```
***
## Complete Example
### Hook Method Complete Example
```tsx theme={null}
import React, { useEffect, useCallback } from 'react';
import {
Button,
View,
Text,
StyleSheet,
Alert,
ActivityIndicator
} from 'react-native';
import { useAdropRewardedAd } from 'adrop-ads-react-native';
const RewardedAdScreen = () => {
const unitId = __DEV__ ? 'PUBLIC_TEST_UNIT_ID_REWARDED' : 'YOUR_PRODUCTION_UNIT_ID';
const {
load,
show,
reset,
isLoaded,
isOpened,
isClosed,
isEarnRewarded,
reward,
errorCode,
isReady
} = useAdropRewardedAd(unitId);
// Load ad on component mount
useEffect(() => {
if (isReady) {
console.log('Ad instance ready, starting ad load');
load();
}
}, [isReady, load]);
// When ad is displayed
useEffect(() => {
if (isOpened) {
console.log('Ad is displayed');
// Pause background music, etc.
}
}, [isOpened]);
// Preload next ad after ad closes
useEffect(() => {
if (isClosed) {
console.log('Ad closed');
// Resume background music, etc.
// Preload next ad
reset();
load();
}
}, [isClosed, reset, load]);
// Handle reward
useEffect(() => {
if (isEarnRewarded && reward) {
console.log(`Reward earned - Type: ${reward.type}, Amount: ${reward.amount}`);
grantReward(reward.type, reward.amount);
}
}, [isEarnRewarded, reward]);
// Handle errors
useEffect(() => {
if (errorCode) {
console.log('Ad error:', errorCode);
// Retry on network error
if (errorCode === AdropErrorCode.network) {
Alert.alert(
'Network Error',
'Unable to load ad. Retrying in 3 seconds.',
[{ text: 'OK' }]
);
setTimeout(() => {
reset();
load();
}, 3000);
} else {
Alert.alert('Error', `Ad error: ${errorCode}`);
}
}
}, [errorCode, reset, load]);
const handleShowAd = useCallback(() => {
if (isLoaded) {
show();
} else {
Alert.alert('Notice', 'Ad not loaded yet.');
}
}, [isLoaded, show]);
const grantReward = (type: number, amount: number) => {
// Grant reward to user
Alert.alert(
'Reward Earned!',
`Type: ${type}, Amount: ${amount}`,
[{ text: 'OK' }]
);
// Implement actual reward logic
// e.g., add coins, restore life, etc.
};
return (
Rewarded Ad
{!isReady && (
Initializing ad...
)}
Status: {isLoaded ? 'Ready' : 'Loading'}
{errorCode && (
Error: {errorCode}
)}
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
backgroundColor: '#f5f5f5',
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 30,
},
loadingContainer: {
alignItems: 'center',
marginBottom: 20,
},
loadingText: {
marginTop: 10,
color: '#666',
},
buttonContainer: {
width: '100%',
marginVertical: 8,
},
statusContainer: {
marginTop: 30,
alignItems: 'center',
},
statusText: {
fontSize: 16,
color: '#333',
},
errorText: {
fontSize: 14,
color: 'red',
marginTop: 8,
},
});
export default RewardedAdScreen;
```
***
## Backfill Ads
To use backfill ads, you must have the backfill dependency added to your native platforms (Android/iOS).
When backfill ads are enabled, the SDK automatically falls back to backfill ads when no direct ad is available. You can handle backfill-specific error codes in the listener.
```tsx theme={null}
rewardedAd.listener = {
onAdReceived: (ad) => {
console.log('Ad loaded:', ad.creativeId)
},
onAdFailedToReceive: (ad, errorCode) => {
switch (errorCode) {
case AdropErrorCode.adNoFill:
console.log('No direct ad available, requesting backfill...')
break
case AdropErrorCode.backfillNoFill:
console.log('No backfill ad available either')
break
default:
console.log('Ad load failed:', errorCode)
}
},
}
```
***
## Related Documentation
Interstitial ad implementation guide
Popup ad implementation guide
Native ad implementation guide
Error code reference
# Splash Ads
Source: https://docs.adrop.io/sdk/react-native/splash
Learn how to implement splash ads in your React Native app.
## Overview
Splash ads are displayed on the splash screen when the app launches. The ad appears alongside your app logo, providing a natural user experience.
### Features
* Natural ad exposure at app launch
* Maintains brand image with app logo
* Fixed ad size: 360dp × 270dp (Android), 360px × 270px (iOS)
* Supports image ads
Use the test unit ID during development: `PUBLIC_TEST_UNIT_ID_SPLASH`
***
## Caching Behavior
Splash ads are prefetched and cached on the device so the creative can appear instantly at launch, before any network request completes. On the next launch the cached creative is shown first, and a fresh ad request runs in the background to refresh the cache for subsequent launches.
Because of this prefetch model, a creative that is **paused** in Ad Control Console may still be shown from the device cache until the next refresh cycle completes on each user's device. Plan creative pauses and replacements with a buffer for the cache to roll over across the install base.
***
## Implementation
Splash ads in React Native apps are implemented through native platform configurations. Since splash ads display before the React Native bridge fully initializes, they must be configured at the native Android and iOS level.
React Native apps use native splash screens that appear before the JavaScript bundle loads. Adrop splash ads integrate with this native splash flow.
***
## Android Configuration
### Step 1: Configure strings.xml
Add the following settings to `android/app/src/main/res/values/strings.xml`.
```xml title="android/app/src/main/res/values/strings.xml" theme={null}
My AppYOUR_SPLASH_UNIT_IDcom.yourcompany.yourapp.MainActivity
```
Use `PUBLIC_TEST_UNIT_ID_SPLASH` instead of `YOUR_SPLASH_UNIT_ID` for testing.
### Step 2: Add Splash Logo Image
Add your app logo to the `android/app/src/main/res/drawable/` folder.
```
android/app/src/main/res/drawable/splash_logo.png
```
Recommended size: 288dp × 288dp (@2x: 576px, @3x: 864px)
### Step 3: Configure AndroidManifest.xml
Set `AdropSplashAdActivity` as the launcher activity.
```xml title="android/app/src/main/AndroidManifest.xml" theme={null}
```
`android:exported="true"` is required for `AdropSplashAdActivity`. Make sure to remove the launcher intent-filter from `MainActivity`.
### Step 4: Configure Splash Theme
Add or update the splash theme in `android/app/src/main/res/values/styles.xml`.
```xml title="android/app/src/main/res/values/styles.xml" theme={null}
```
The `androidx.core:core-splashscreen` dependency is included in the Adrop SDK. No additional setup is required.
### Step 5: Customize Layout (Optional)
To use a custom layout, create `activity_adrop_splash_ad.xml` in `android/app/src/main/res/layout/`.
```xml title="android/app/src/main/res/layout/activity_adrop_splash_ad.xml" theme={null}
```
An `ImageView` with `android:id="@+id/adrop_splash_ad_image"` is required. The ad will be displayed in this view.
### Step 6: Configure Display Duration (Optional)
To change the default display duration, create `android/app/src/main/res/values/integers.xml`.
```xml title="android/app/src/main/res/values/integers.xml" theme={null}
10002000
```
### Step 7: Implement AdropSplashAd Listener (Optional)
To receive splash ad events, set up a listener in your `Application` class.
```kotlin Kotlin theme={null}
import android.app.Application
import io.adrop.ads.Adrop
import io.adrop.ads.splash.AdropSplashAd
import io.adrop.ads.splash.AdropSplashAdListener
import io.adrop.ads.model.AdropErrorCode
class MainApplication : Application() {
private lateinit var splashAd: AdropSplashAd
override fun onCreate() {
super.onCreate()
// Initialize SDK
Adrop.initialize(this, production = false)
// Configure splash ad
splashAd = AdropSplashAd(this) { splashAd ->
// shouldSkip: Determines whether to skip splash ad
// Return true to skip the ad and go directly to the main screen
checkShouldSkipSplash()
}
splashAd.splashAdListener = object : AdropSplashAdListener {
override fun onAdReceived(ad: AdropSplashAd) {
println("Splash ad received successfully")
}
override fun onAdFailedToReceive(ad: AdropSplashAd, errorCode: AdropErrorCode) {
println("Failed to receive splash ad: $errorCode")
}
override fun onAdImpression(ad: AdropSplashAd) {
println("Splash ad impression")
}
override fun onAdClose(ad: AdropSplashAd, impressed: Boolean) {
println("Splash ad closed - impressed: $impressed")
}
}
}
private fun checkShouldSkipSplash(): Boolean {
// Example: Skip splash when entering via deep link or under certain conditions
return false
}
}
```
```java Java theme={null}
import android.app.Application;
import io.adrop.ads.Adrop;
import io.adrop.ads.splash.AdropSplashAd;
import io.adrop.ads.splash.AdropSplashAdListener;
import io.adrop.ads.model.AdropErrorCode;
public class MainApplication extends Application {
private AdropSplashAd splashAd;
@Override
public void onCreate() {
super.onCreate();
// Initialize SDK
Adrop.INSTANCE.initialize(this, false, new String[]{});
// Configure splash ad
splashAd = new AdropSplashAd(this, ad -> checkShouldSkipSplash());
splashAd.setSplashAdListener(new AdropSplashAdListener() {
@Override
public void onAdReceived(AdropSplashAd ad) {
System.out.println("Splash ad received successfully");
}
@Override
public void onAdFailedToReceive(AdropSplashAd ad, AdropErrorCode errorCode) {
System.out.println("Failed to receive splash ad: " + errorCode);
}
@Override
public void onAdImpression(AdropSplashAd ad) {
System.out.println("Splash ad impression");
}
@Override
public void onAdClose(AdropSplashAd ad, boolean impressed) {
System.out.println("Splash ad closed - impressed: " + impressed);
}
});
}
private boolean checkShouldSkipSplash() {
// Determine whether to skip splash ad
return false;
}
}
```
When `shouldSkip` returns `true`, the ad is not loaded and navigates immediately to the main screen.
### Step 8: Running the App
When running the Android app with React Native CLI, you need to specify the main activity:
```bash theme={null}
npx react-native run-android --main-activity io.adrop.ads.splash.AdropSplashAdActivity
```
This is required because `AdropSplashAdActivity` is set as the launcher activity instead of `MainActivity`.
***
## iOS Configuration
### Step 1: Configure AppDelegate
Update your `ios/YourApp/AppDelegate.mm` (or `AppDelegate.swift`) to display splash ads.
```swift Swift theme={null}
import UIKit
import React
import AdropAds
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// Initialize Adrop SDK
Adrop.initialize(production: false)
return true
}
}
```
```objc Objective-C theme={null}
#import "AppDelegate.h"
#import
@import AdropAds;
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Initialize Adrop SDK
[Adrop initializeWithProduction:NO useInAppBrowser:NO targetCountries:@[]];
self.moduleName = @"YourApp";
self.initialProps = @{};
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@end
```
### Step 2: Configure SceneDelegate (iOS 13+)
For apps using SceneDelegate, create or update `ios/YourApp/SceneDelegate.swift`.
```swift title="ios/YourApp/SceneDelegate.swift" theme={null}
import UIKit
import React
import AdropAds
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(
_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions
) {
guard let windowScene = (scene as? UIWindowScene) else { return }
window = UIWindow(windowScene: windowScene)
// Create React Native root view
let bridge = RCTBridge(delegate: self, launchOptions: nil)!
let rootView = RCTRootView(bridge: bridge, moduleName: "YourApp", initialProperties: nil)
let rootViewController = UIViewController()
rootViewController.view = rootView
#if DEBUG
let splashUnitId = "PUBLIC_TEST_UNIT_ID_SPLASH"
#else
let splashUnitId = "YOUR_PRODUCTION_SPLASH_UNIT_ID"
#endif
// Create splash ad view controller
let splashViewController = AdropSplashAdViewController(
unitId: splashUnitId
)
splashViewController.delegate = self
splashViewController.logoImage = UIImage(named: "splash_logo")
splashViewController.backgroundColor = .white
splashViewController.mainViewController = rootViewController
window?.rootViewController = splashViewController
window?.makeKeyAndVisible()
}
}
// MARK: - AdropSplashAdDelegate
extension SceneDelegate: AdropSplashAdDelegate {
func onAdClose(_ ad: AdropSplashAd, impressed: Bool) {
print("Splash ad closed - impressed: \(impressed)")
}
func onAdReceived(_ ad: AdropSplashAd) {
print("Splash ad received")
}
func onAdFailedToReceive(_ ad: AdropSplashAd, _ errorCode: AdropErrorCode) {
print("Splash ad failed: \(errorCode)")
}
func onAdImpression(_ ad: AdropSplashAd) {
print("Splash ad impression")
}
}
```
`mainViewController` must be set. After the splash ad closes, the SDK transitions to this view controller to display the React Native app.
### Step 3: Add Logo Image
Add your app logo to the iOS assets:
1. Open Xcode and navigate to `YourApp/Images.xcassets`
2. Add a new Image Set named `splash_logo`
3. Add your logo images (@1x, @2x, @3x)
Recommended size: 200×200 \~ 300×300 points
### Step 4: Configure Info.plist
If using SceneDelegate, ensure your `Info.plist` has the scene configuration:
```xml title="ios/YourApp/Info.plist" theme={null}
UIApplicationSceneManifestUIApplicationSupportsMultipleScenesUISceneConfigurationsUIWindowSceneSessionRoleApplicationUISceneConfigurationNameDefault ConfigurationUISceneDelegateClassName$(PRODUCT_MODULE_NAME).SceneDelegate
```
***
## AdropSplashAdDelegate
| Method | Required | Description |
| - | - | - |
| `onAdClose(_:impressed:)` | Optional | Called when splash ad closes. `impressed` indicates if ad was displayed |
| `onAdReceived(_:)` | Optional | Called when ad is received successfully |
| `onAdFailedToReceive(_:_:)` | Optional | Called when ad fails to receive |
| `onAdImpression(_:)` | Optional | Called when ad impression is recorded |
***
## Best Practices
### 1. Set Appropriate Timers
Configure reasonable timeout values for better user experience.
```xml theme={null}
10002000
```
On iOS, the splash ad display duration is configured in the Adrop console ad unit settings.
### 2. Optimize Logo Image
Prepare your app logo in appropriate sizes to minimize loading time.
**Android:**
```
drawable-mdpi/splash_logo.png (192px × 192px)
drawable-hdpi/splash_logo.png (288px × 288px)
drawable-xhdpi/splash_logo.png (384px × 384px)
drawable-xxhdpi/splash_logo.png (576px × 576px)
drawable-xxxhdpi/splash_logo.png (768px × 768px)
```
**iOS:**
```
splash_logo@1x.png (200px × 200px)
splash_logo@2x.png (400px × 400px)
splash_logo@3x.png (600px × 600px)
```
### 3. Handle Failures Gracefully
Ensure the app starts normally even when ad loading fails.
```swift theme={null}
func onAdFailedToReceive(_ ad: AdropSplashAd, _ errorCode: AdropErrorCode) {
print("Ad load failed: \(errorCode)")
// AdropSplashAdViewController automatically calls onAdClose
}
```
### 4. Distinguish Test/Production Environments
```swift theme={null}
#if DEBUG
let isProduction = false
let splashUnitId = "PUBLIC_TEST_UNIT_ID_SPLASH"
#else
let isProduction = true
let splashUnitId = "YOUR_PRODUCTION_SPLASH_UNIT_ID"
#endif
Adrop.initialize(production: isProduction)
let splashViewController = AdropSplashAdViewController(unitId: splashUnitId)
```
***
## Testing
### Test Unit ID
Use the test unit ID during development.
```
PUBLIC_TEST_UNIT_ID_SPLASH
```
Make sure to use the actual unit ID created in the Adrop console for production releases. No ad revenue is generated with test unit IDs.
***
## Troubleshooting
* Verify that `AdropSplashAdActivity` is set as LAUNCHER in AndroidManifest.xml
* Check `android:exported="true"` setting
* Verify SDK initialization is complete
* Ensure `adrop_service.json` is in the `android/app/src/main/assets` folder
* Verify `window.rootViewController` is set correctly
* Check if SceneDelegate is configured in Info.plist
* Ensure SDK initialization is complete
* Verify `adrop_service.json` is added to the Xcode project
* Check network connection status
* Verify the unit ID is correct
* Confirm `production: false` setting in test environment
* Check error code in `onAdFailedToReceive`
* Verify `adrop_splash_ad_next_activity` is set correctly (Android)
* Check `onAdClose` delegate implementation (iOS)
* Ensure RCTBridge and RCTRootView are created properly
* Verify filename is exactly `activity_adrop_splash_ad.xml`
* Check that ImageView with `@+id/adrop_splash_ad_image` exists
* Clean build and re-run
***
## Next Steps
Display full-screen ads during screen transitions
Implement banner ads
Custom design native ads
Provide user rewards
# Targeting Settings
Source: https://docs.adrop.io/sdk/react-native/targeting
Learn how to set up targeting in the React Native SDK.
## Overview
Targeting settings allow you to show personalized ads to specific user groups. The Adrop SDK provides **audience targeting**.
* **Audience Targeting**: Display ads based on user attributes (properties).
Context targeting (passing a Context ID) is currently not supported in the Flutter and React Native SDKs.
To collect targeting data, you must set properties before loading ads.
***
## UID Setup (Optional)
Set a user identifier (UID) to share targeting data across platforms. UID is optional and is not required for property-based or event-based targeting.
### Usage
```typescript theme={null}
import { Adrop } from 'adrop-ads-react-native'
// After user login (optional)
Adrop.setUID("user_123")
// On user logout
Adrop.setUID("")
```
### Parameters
| Parameter | Type | Description |
| - | - | - |
| `uid` | `string` | Unique user identifier (e.g., service member ID) |
UID is transmitted hashed with SHA-256. Do not pass personal information (email, phone number, etc.) directly.
Pass an empty string (`""`) on logout to reset the UID.
***
## Audience Targeting
Collect user attribute information (properties) to show ads to specific user groups.
### Setting Properties
Use the `AdropMetrics.setProperty()` method to collect user attribute information.
```typescript theme={null}
import { AdropMetrics } from 'adrop-ads-react-native'
// String property
AdropMetrics.setProperty("membership_level", "premium")
// Number property
AdropMetrics.setProperty("booking_count", 15)
// Boolean property
AdropMetrics.setProperty("is_subscriber", true)
// Pass null (remove property)
AdropMetrics.setProperty("membership_level", null)
```
### Retrieving Properties
Use `AdropMetrics.properties()` to retrieve all currently set properties.
```typescript theme={null}
import { AdropMetrics } from 'adrop-ads-react-native'
const currentProperties = await AdropMetrics.properties()
console.log(currentProperties)
// { membership_level: "premium", booking_count: 15, ... }
```
Returns all currently set properties as a key-value object. Returns an empty object if no properties are set.
### Parameters
| Parameter | Type | Description |
| - | - | - |
| `key` | `string` | Property key (max 64 characters) |
| `value` | `any` | Property value (string, number, boolean, null) |
* Property keys can be up to 64 characters.
* String values can be up to 256 characters.
* Number values can be up to 9007199254740991.
* A maximum of 256 properties can be set.
### Built-in Properties
The Adrop SDK provides built-in properties for targeting. Use `AdropProperties` and `AdropGender` enums to set values.
#### Age (Birthday)
When you pass birthday information, age is automatically calculated.
```typescript theme={null}
import { AdropMetrics } from 'adrop-ads-react-native'
import { AdropProperties } from 'adrop-ads-react-native'
// Year only (yyyy)
AdropMetrics.setProperty(AdropProperties.BIRTH, "1990")
// Year and month (yyyyMM)
AdropMetrics.setProperty(AdropProperties.BIRTH, "199003")
// Full date (yyyyMMdd)
AdropMetrics.setProperty(AdropProperties.BIRTH, "19900315")
```
**Date Formats**
| Format | Example | Description |
| - | - | - |
| `yyyy` | "1990" | Year only |
| `yyyyMM` | "199003" | Year and month |
| `yyyyMMdd` | "19900315" | Full date |
#### Gender
```typescript theme={null}
import { AdropMetrics } from 'adrop-ads-react-native'
import { AdropProperties, AdropGender } from 'adrop-ads-react-native'
// Male
AdropMetrics.setProperty(AdropProperties.GENDER, AdropGender.MALE)
// Female
AdropMetrics.setProperty(AdropProperties.GENDER, AdropGender.FEMALE)
// Other
AdropMetrics.setProperty(AdropProperties.GENDER, AdropGender.OTHER)
// Unknown
AdropMetrics.setProperty(AdropProperties.GENDER, AdropGender.UNKNOWN)
```
**Gender Constants**
| Constant | Value | Description |
| - | - | - |
| `AdropGender.MALE` | `"M"` | Male |
| `AdropGender.FEMALE` | `"F"` | Female |
| `AdropGender.OTHER` | `"O"` | Other |
| `AdropGender.UNKNOWN` | `"U"` | Unknown |
#### Setting Age Directly
You can also set age directly instead of birthday.
```typescript theme={null}
import { AdropMetrics } from 'adrop-ads-react-native'
import { AdropProperties } from 'adrop-ads-react-native'
AdropMetrics.setProperty(AdropProperties.AGE, 30)
```
You only need to set one of `BIRTH` or `AGE`. If both are set, `BIRTH` takes priority.
### Custom Properties
You can set custom properties for your service. Custom properties must be defined first in the [Ad Control Console](https://console.adrop.io) under the **Property** menu.
```typescript theme={null}
import { AdropMetrics } from 'adrop-ads-react-native'
// Local activity app example
AdropMetrics.setProperty("region", "Seoul")
AdropMetrics.setProperty("booking_count", 5)
// Shopping mall app example
AdropMetrics.setProperty("membership_tier", "gold")
AdropMetrics.setProperty("total_purchase_amount", 1500000)
// Media app example
AdropMetrics.setProperty("favorite_genre", "drama")
AdropMetrics.setProperty("is_premium_subscriber", true)
```
Custom property names must exactly match the names defined in the console. Case-sensitive.
### Property Usage Example
Example of setting properties when a user logs into the app.
```typescript theme={null}
import React, { useEffect } from 'react'
import { AdropMetrics } from 'adrop-ads-react-native'
import { AdropProperties, AdropGender } from 'adrop-ads-react-native'
const ProfileScreen = () => {
useEffect(() => {
// Get user info
const user = getUserInfo()
// Set built-in properties
AdropMetrics.setProperty(AdropProperties.BIRTH, user.birthDate)
AdropMetrics.setProperty(AdropProperties.GENDER, user.gender)
// Set custom properties
AdropMetrics.setProperty("membership_level", user.membershipLevel)
AdropMetrics.setProperty("total_booking_count", user.bookingCount)
}, [])
return (
// Screen component
)
}
export default ProfileScreen
```
***
## Event Tracking
Track user behavior events to build audiences based on user actions. Use `AdropMetrics.sendEvent()` to send events.
`logEvent()` is deprecated. Use `sendEvent()` instead.
### Sending Events
```typescript theme={null}
import { AdropMetrics } from 'adrop-ads-react-native'
// Simple event (no parameters)
AdropMetrics.sendEvent('app_open')
// Event with parameters
AdropMetrics.sendEvent('view_item', {
item_id: 'SKU-123',
item_name: 'Widget',
item_category: 'Electronics',
brand: 'BrandX',
price: 29900,
})
```
### Multi-Item Events
For events that involve multiple items (e.g., purchase, begin\_checkout), pass an `items` array in the parameters.
```typescript theme={null}
import { AdropMetrics } from 'adrop-ads-react-native'
AdropMetrics.sendEvent('purchase', {
tx_id: 'TXN-20240101-001',
currency: 'KRW',
items: [
{
item_id: 'SKU-001',
item_name: 'Product A',
item_category: 'Electronics',
brand: 'BrandX',
price: 29900,
quantity: 1,
},
{
item_id: 'SKU-002',
item_name: 'Product B',
price: 15000,
quantity: 2,
},
],
})
```
### Supported Event Examples
| Event Name | Description | Common Parameters |
| - | - | - |
| `app_open` | App opened | — |
| `sign_up` | User signed up | `method` |
| `push_clicks` | Push notification clicked | `campaign_id` |
| `page_view` | Page viewed | `page_id`, `page_category`, `page_url` |
| `click` | Element clicked | `element_id`, `element_type`, `target_url` |
| `view_item` | Viewed item | `item_id`, `item_name`, `item_category`, `brand`, `price` |
| `view_content` | Viewed content | `content_id`, `content_name`, `content_type` |
| `add_to_wishlist` | Added to wishlist | `item_id`, `item_name`, `item_category`, `brand`, `price` |
| `add_to_cart` | Added to cart | `item_id`, `item_name`, `item_category`, `brand`, `price`, `quantity`, `value`, `currency` |
| `begin_lead_form` | Began lead form | `form_id`, `form_name`, `form_type`, `form_destination` |
| `generate_lead` | Lead generated | `form_id`, `form_name`, `form_type`, `form_destination`, `value`, `currency` |
| `begin_checkout` | Began checkout | `currency`, `items` |
| `purchase` | Purchase completed | `tx_id`, `currency`, `items` |
* Event name must be 1–64 characters.
* Parameter key must be 1–64 characters.
* String parameter values are limited to 1024 characters.
***
## Best Practices
### 1. Set Properties Before Loading Ads
```typescript theme={null}
// Correct
AdropMetrics.setProperty("membership_level", "premium")
// Load ads...
// Wrong
// Load ads...
AdropMetrics.setProperty("membership_level", "premium") // Setting after ad load means targeting won't apply
```
### 2. Update Properties When They Change
```typescript theme={null}
import { AdropMetrics } from 'adrop-ads-react-native'
const App = () => {
useEffect(() => {
// Set initial properties at app start
updateUserProperties()
}, [])
const onUserPurchaseComplete = (purchaseAmount: number) => {
// Update property on purchase completion
const currentTotal = getCurrentTotalPurchase()
AdropMetrics.setProperty("total_purchase_amount", currentTotal + purchaseAmount)
}
const onMembershipUpgrade = (newTier: string) => {
// Update property on membership upgrade
AdropMetrics.setProperty("membership_tier", newTier)
}
// ...
}
```
### 3. Reset Properties on Logout
```typescript theme={null}
import { AdropMetrics } from 'adrop-ads-react-native'
const onUserLogout = () => {
// Reset properties if needed
AdropMetrics.setProperty("membership_level", null)
AdropMetrics.setProperty("total_booking_count", null)
}
```
***
## Related Documentation
Create audience targeting in the console
Set up targeting category sales
Implementing banner ads
Implementing native ads
***
## FAQ
Ads will still be shown without setting a UID. UID is optional and used for sharing targeting data across platforms. Property-based and event-based targeting work without UID.
Property data is reflected in the console within 24 hours of collection. If data is not showing:
1. Verify the property is correctly defined in the console.
2. Check that the property key passed from the SDK exactly matches the console (case-sensitive).
3. Verify `AdropMetrics.setProperty()` is called before loading ads.
Pass `null` as the property value to delete it:
```typescript theme={null}
AdropMetrics.setProperty("membership_level", null)
```
The React Native SDK does not currently support array types directly. If needed, convert to a string or split into multiple individual properties.
# UMP Integration
Source: https://docs.adrop.io/sdk/react-native/ump
Integrate Google UMP for GDPR/CCPA compliance in React Native
## Overview
The User Messaging Platform (UMP) SDK helps you manage user consent for personalized advertising in compliance with GDPR (General Data Protection Regulation) and CCPA (California Consumer Privacy Act).
UMP integration requires the `adrop-ads-backfill` native module. Make sure you have completed the [Getting Started](/sdk/react-native/overview) installation guide first.
***
## Basic Usage
Use the AdropConsent module to request user consent after initializing Adrop.
```typescript theme={null}
import { AdropConsent } from 'adrop-ads-react-native'
// Call after Adrop.initialize()
const requestConsent = async () => {
const result = await AdropConsent.requestConsentInfoUpdate()
if (result.error) {
console.error('Consent error:', result.error)
}
}
```
***
## AdropConsentResult
`requestConsentInfoUpdate()` returns the following result type:
```typescript theme={null}
interface AdropConsentResult {
status: AdropConsentStatus
canRequestAds: boolean
canShowPersonalizedAds: boolean
error?: string
}
```
| Field | Type | Description |
| - | - | - |
| `status` | `AdropConsentStatus` | Current consent status |
| `canRequestAds` | `boolean` | Whether ad requests are allowed |
| `canShowPersonalizedAds` | `boolean` | Whether personalized ads can be shown |
| `error` | `string` | Error message (if any) |
***
## AdropConsentStatus
```typescript theme={null}
enum AdropConsentStatus {
UNKNOWN = 0,
REQUIRED = 1,
NOT_REQUIRED = 2,
OBTAINED = 3,
}
```
| Status | Value | Description |
| - | - | - |
| `UNKNOWN` | `0` | Consent status not yet determined |
| `REQUIRED` | `1` | Consent required (popup will be shown) |
| `NOT_REQUIRED` | `2` | Consent not required (non-GDPR region) |
| `OBTAINED` | `3` | Consent already obtained |
***
## Privacy Options
Users must be able to change their consent after the first prompt. When UMP reports that a privacy options entry point is required, add one to your app — typically a **Privacy settings** item in your settings screen — and open the privacy options form from it.
Requires `adrop-ads-react-native` 1.15.0 or later, with the backfill module also on 1.15.0 or later — with an older backfill the status always returns `UNKNOWN` and the form is not shown.
### Check whether an entry point is required
```typescript theme={null}
import {
AdropConsent,
AdropPrivacyOptionsRequirementStatus,
} from 'adrop-ads-react-native'
// Re-check whenever your settings screen appears
const refreshPrivacyOptions = async () => {
const status = await AdropConsent.getPrivacyOptionsRequirementStatus()
// Show the entry point only when it is required
setPrivacyOptionsRequired(
status === AdropPrivacyOptionsRequirementStatus.REQUIRED
)
}
```
| Status | Description |
| - | - |
| `UNKNOWN` | Not determined yet — `requestConsentInfoUpdate()` has not completed in this app session, or the installed backfill does not support privacy options |
| `REQUIRED` | An entry point must be shown |
| `NOT_REQUIRED` | No entry point is needed |
Call `requestConsentInfoUpdate()` on **every app launch**, including for users who already gave consent. The requirement status reflects the current app session only after that call completes — otherwise returning users never see the entry point. The consent form itself is still shown only when it is needed.
### Show the privacy options form
```typescript theme={null}
// Open the form from your "Privacy settings" entry point
const showPrivacyOptionsForm = async () => {
const result = await AdropConsent.showPrivacyOptionsForm()
if (result.error) {
console.error('Error:', result.error)
return
}
// Consent may have changed — re-check whether the entry point is still required
await refreshPrivacyOptions()
}
```
When the form closes, the SDK applies the updated consent and then returns an `AdropConsentResult`. The form opens only while an entry point is required; otherwise the stored consent is left unchanged and the result carries the current state with an error.
`getPrivacyOptionsRequirementStatus()` resolves `UNKNOWN` instead of rejecting when the consent manager is not available.
***
## Debug Settings (Test Mode)
Test GDPR/CCPA consent flows during development:
```typescript theme={null}
import {
AdropConsent,
AdropConsentDebugGeography,
} from 'adrop-ads-react-native'
// Set debug geography before requesting consent
// Device ID is automatically detected
AdropConsent.setDebugSettings(AdropConsentDebugGeography.EEA) // Test GDPR
// Reset consent for testing
AdropConsent.reset()
```
### Debug Geographies
| Geography | Description |
| - | - |
| `DISABLED` | Use actual device location |
| `EEA` | Test GDPR (European Economic Area) |
| `REGULATED_US_STATE` | Test CCPA (California, etc.) |
| `OTHER` | Test non-regulated regions |
Debug settings should only be used during development. Remove or disable them before releasing to production.
`reset()` is for testing only — never call it in production code. To let users change their consent, use `showPrivacyOptionsForm()`.
***
## Additional Methods
### getConsentStatus
```typescript theme={null}
static getConsentStatus(): Promise
```
Returns the current consent status.
### canRequestAds
```typescript theme={null}
static canRequestAds(): Promise
```
Returns whether ad requests are currently allowed.
***
## Complete Example
```typescript theme={null}
import React, { useEffect, useState } from 'react'
import { View, Text, Button } from 'react-native'
import {
Adrop,
AdropConsent,
AdropConsentStatus,
AdropConsentDebugGeography,
} from 'adrop-ads-react-native'
const App = () => {
const [consentStatus, setConsentStatus] = useState(
AdropConsentStatus.UNKNOWN
)
useEffect(() => {
initializeConsent()
}, [])
const initializeConsent = async () => {
// Initialize Adrop first
Adrop.initialize(false)
// Set debug settings in development
if (__DEV__) {
AdropConsent.setDebugSettings(AdropConsentDebugGeography.EEA)
}
// Request consent info update
const result = await AdropConsent.requestConsentInfoUpdate()
if (result.error) {
console.error('Consent error:', result.error)
} else {
setConsentStatus(result.status)
}
}
const resetConsent = async () => {
AdropConsent.reset()
await initializeConsent()
}
return (
Consent Status: {AdropConsentStatus[consentStatus]}
{__DEV__ && (
)}
)
}
export default App
```
***
## Best Practices
Request consent as early as possible in your app lifecycle, ideally right after SDK initialization, and call it on every app launch.
Always handle consent errors gracefully and provide fallback behavior.
Use debug settings to test all consent scenarios (required, not required, obtained) before release.
When the privacy options status is required, show an entry point in your settings screen so users can change their consent at any time.
***
## Related Documentation
Set up audience targeting
SDK installation and setup guide
Types, methods, and error codes
# WebView Guide
Source: https://docs.adrop.io/sdk/react-native/webview
How to display backfill ads in WebView for React Native
## Overview
Display backfill ads in WebView within your React Native app. The Adrop SDK provides a `useAdropWebView` hook for easy integration, as well as a manual `registerWebView` API.
***
## Prerequisites
Install the `react-native-webview` package in your project.
```bash npm theme={null}
npm install react-native-webview
```
```bash yarn theme={null}
yarn add react-native-webview
```
WebView backfill ads require `adrop-ads-backfill` to be configured on both Android and iOS. See [Getting Started](/sdk/react-native/overview) for setup instructions.
***
## Platform Configuration
Configure the native platforms to enable backfill ads in WebView.
Add the following to `android/app/src/main/AndroidManifest.xml`:
```xml AndroidManifest.xml theme={null}
```
Add the following key to `ios/{YourApp}/Info.plist`:
```xml Info.plist theme={null}
GADIntegrationManagerwebview
```
This configuration is required for backfill ads to work properly in WebView. Without it, backfill ads may not display.
***
## Using useAdropWebView Hook
The recommended approach. The `useAdropWebView` hook handles WebView registration automatically.
```tsx theme={null}
import React from 'react'
import { View } from 'react-native'
import { WebView } from 'react-native-webview'
import { useAdropWebView } from 'adrop-ads-react-native'
const WebViewScreen: React.FC = () => {
const { containerRef, isReady, onLayout } = useAdropWebView()
return (
)
}
```
**Return values:**
| Property | Type | Description |
| - | - | - |
| `containerRef` | `React.RefObject` | Ref to attach to the `View` wrapping the WebView |
| `isReady` | `boolean` | Whether the WebView has been successfully registered |
| `onLayout` | `() => void` | Callback to trigger registration on layout |
Load web content only after `isReady` is `true`. Before registration is complete, passing a URL to the WebView may result in ads not displaying.
***
## Manual Registration
You can also register the WebView manually using `Adrop.registerWebView`.
```tsx theme={null}
import React, { useEffect, useRef, useState } from 'react'
import { findNodeHandle, View } from 'react-native'
import { WebView } from 'react-native-webview'
import { Adrop } from 'adrop-ads-react-native'
const WebViewScreen: React.FC = () => {
const containerRef = useRef(null)
const [isReady, setIsReady] = useState(false)
useEffect(() => {
const register = async () => {
if (containerRef.current) {
const tag = findNodeHandle(containerRef.current)
if (tag != null) {
await Adrop.registerWebView(tag)
setIsReady(true)
}
}
}
register()
}, [])
return (
)
}
```
`react-native-webview`'s ref is an imperative handle, so `findNodeHandle` does not work directly on it. You must wrap the WebView in a `View` and use that `View`'s ref instead.
***
## WebView Configuration
The following WebView props are required for backfill ads to work properly:
| Prop | Value | Description |
| - | - | - |
| `javaScriptEnabled` | `true` | Enable JavaScript execution |
| `thirdPartyCookiesEnabled` | `true` | Enable third-party cookies (Android) |
| `mediaPlaybackRequiresUserAction` | `false` | Allow media autoplay |
| `allowsInlineMediaPlayback` | `true` | Allow inline media playback (iOS) |
***
## Handling External URLs
If your app opens non-own-domain URLs in an external browser, you must ensure that ad resource requests (e.g., `googleads.g.doubleclick.net`) are not blocked.
Ad resources such as iframes and scripts are loaded automatically by the ad SDK — they are not user-initiated navigations. Only redirect **user-initiated navigations** to an external browser.
```tsx theme={null}
import { Linking } from 'react-native'
const allowedHost = 'my-domain.com'
{
const url = request.url
const host = new URL(url).host
// Only redirect user-initiated navigations to external browser
// Ad resources (e.g., googleads.g.doubleclick.net) load via iframes/scripts,
// not user clicks — do not block them
if (!host.includes(allowedHost) && request.navigationType === 'click') {
Linking.openURL(url)
return false
}
return true
}}
/>
```
Do not block or redirect all non-own-domain requests. Ad resources like `googleads.g.doubleclick.net` are loaded automatically during the backfill ad process and must be allowed to load within the WebView.
***
## Related Documentation
Learn how to integrate banner ads
API reference
Example repository
# Backfill Revenue Payout
Source: https://docs.adrop.io/settlement/backfill
Guide to revenue settlement from backfill ads.
To receive revenue settlements, business information and payout account must be registered. Check the registration method in [Revenue Payout](/ad-platform/payout).
***
## What is Backfill Revenue?
Revenue generated when ads are served from backfill ad networks when direct ads are not available. Revenue is aggregated per unit.
For backfill ad setup, refer to [Maximize Revenue with Backfill Ads](/ad-unit/backfill).
***
## Settlement Schedule
| **Timing** | **Details** |
| - | - |
| **Within 5 business days after backfill revenue month (M) + 1 month** | Confirmed revenue statement sent via email |
| **M+1 month, 10th** | Dispute deadline, tax invoice issuance deadline (Invoice date: last day of campaign end month) |
| **Within 30 days from invoice date** | Revenue payout (next business day if weekend/holiday) |
**Example: June revenue**
Backfill revenue generated
Tax invoice issued
Revenue payout
***
## Exchange Rate Application
Backfill revenue is paid in KRW. The exchange rate is based on the monthly average of the KRW/USD base rate for the revenue generation month (M), as published by the [Bank of Korea Economic Statistics System](https://ecos.bok.or.kr). When converting to KRW, amounts less than 1 KRW are rounded up.
***
## Checking Revenue Statements
You can check backfill revenue settlement history and statements in Management > Revenue Payout section.
***
## Next Steps
Check Ad Control usage fees.
***
## FAQ
Yes. Tax invoices are issued the same as for campaign revenue.
Data aggregation may take up to 24 hours. Please check again later.
# Campaign Revenue Payout
Source: https://docs.adrop.io/settlement/campaign
Guide to the revenue settlement process after campaign completion.
To receive revenue settlements, business information and payout account must be registered. Check the registration method in [Revenue Payout](/ad-platform/payout).
***
## What is Settlement?
When a campaign ends, you receive settlement of the publisher's share from the amount paid by the advertiser. Settlement is processed monthly based on campaign end dates.
***
### Settlement Schedule
| **Timing** | **Details** |
| - | - |
| **Within 5 business days after campaign end month (M)** | Confirmed revenue statement sent via email |
| **M+1 month, 10th** | Dispute deadline, tax invoice issuance deadline (Invoice date: last day of campaign end month) |
| **Within 30 days from invoice date** | Revenue payout (next business day if weekend/holiday) |
**Example: Campaign ending in June**
Campaign ends
Tax invoice issued
Revenue payout
***
## Process by Payment Method
### Card Payment (Prepayment)
This is when an advertiser prepays by card when booking a campaign. Ad Control receives the payment amount from the advertiser.
| Item | Details |
| - | - |
| **Statement Issuance** | Within 5 business days of the month following campaign end |
| **Revenue Payout** | 21st of each month (or next business day) if no disputes on statement |
| **Tax Invoice** | Must be issued by the 10th of each month |
| **Payment Info Changes** | Request by the 10th of each month (changes after the 20th reflected next month) |
Campaign-specific statements can be viewed on the campaign detail page.
### Bank Transfer
| Item | Details |
| - | - |
| **Revenue Payout** | 21st of each month (or next business day) |
| **Payment Info Changes** | Must be completed by the 20th of each month |
| **Payout Conditions** | Paid when accumulated balance meets payout threshold |
| **Carryover** | Carried over to next month if below threshold or payout on hold |
### Manual Payment (Postpayment)
This is when publishers settle directly with advertisers.
* Publishers bill advertisers directly
* Tax invoice issuance also negotiated directly
***
## Automatic Refunds
If campaign performance falls short of goals after completion, the difference is automatically refunded.
| Condition | Description |
| - | - |
| **Target** | CPM, CPC campaigns |
| **Payment Method** | Card payment only |
| **Processing** | Refund amount deducted from settlement amount |
For manual payment campaigns, publishers and advertisers negotiate directly.
***
## Checking Revenue Statements
You can check campaign revenue settlement history and statements in Management > Revenue Payout section.
* Generated month (M), following month (M+1)
* Invoice date: Last day of revenue generation month
* Revenue payout: Within 30 days from invoice date
***
## Next Steps
Check backfill ad revenue settlement.
***
## FAQ
Reply to the settlement email by the 10th of each month to file a dispute.
Payment is made on the next business day.
Yes. Statements are sent, but actual settlement must be processed directly with the advertiser.
# What is Revenue Settlement?
Source: https://docs.adrop.io/settlement/overview
Guide to campaign revenue settlement and Ad Control infrastructure billing.
***
## What are Revenue Settlement and Billing?
There are two financial flows in Ad Control.
### Revenue Settlement (Money Publishers Receive)
When an ad campaign ends, publishers receive settlement for ad revenue.
* **Campaign Revenue**: Settlement of the publisher's share from the campaign budget paid by advertisers
* **Backfill Revenue**: Settlement of revenue generated from backfill ad networks when there are no direct ads
### Infrastructure Billing (Money Publishers Pay)
Infrastructure costs based on Ad Control platform usage are billed monthly.
* Pay-as-you-go billing based on ad impressions
* Free usage quota provided according to subscription plan
***
## Settlement Flow
The settlement process begins when a campaign ends.
Settlement statements are sent via email within 5 business days of the following month.
Disputes on settlement statements can be filed until the 10th of each month.
Revenue is paid on the 21st of each month (next business day if weekend/holiday).
***
## Table of Contents
1. ⚡ [Quickstart: Upgrade Your Plan](/settlement/quickstart)
2. [Campaign Revenue Payout](/settlement/campaign)
3. [Backfill Revenue Payout](/settlement/backfill)
4. [Subscription and Billing](/settlement/subscription)
# Quickstart: Upgrade Your Plan
Source: https://docs.adrop.io/settlement/quickstart
This guide shows you how to start your ad business by upgrading to a paid plan.
> ⏱️ Time required: About 3 minutes
***
## Why Upgrade?
The free plan (Stream) can only be used for testing purposes. To officially operate your ad platform, you need to upgrade to the Wave plan or higher.
* Stream plan: Service restrictions when free usage (10,000 image impressions/month) is exceeded
* Wave plan: Pay-as-you-go billing when free usage is exceeded, no service restrictions
***
## Step-by-Step Guide
Go to Settings > **Subscription and Billing** tab.
Select the **Wave** plan and upgrade.
Click **Add Card** to register a payment method.
***
## Complete
Once the plan upgrade is complete, you can officially operate your ad platform. The "Service in Development" warning that was shown to advertisers will also disappear.
***
## Next Steps
**Learn More**
Learn how to receive your ad revenue earnings.
***
## FAQ
No. Up to the free usage quota (10,000 impressions/month) is free. Only overage is billed in the following month.
Yes. You can change plans in Settings > Subscription and Billing.
No. A payment method is required to bill overage charges.
# Subscription and Billing
Source: https://docs.adrop.io/settlement/subscription
Guide to pricing plans, payment methods, and usage billing for Ad Control platform usage.
You can check this in Settings > **Subscription and Billing** tab.
***
## Pricing Plans
To officially operate your ad platform, you must use a paid plan (Wave or higher). The free plan (Stream) can only be used for testing purposes, and service is restricted when free usage is exceeded.
| Plan | Free Usage | When Exceeded |
| - | - | - |
| **Stream** | 10,000 image impressions/month | Service restrictions |
| **Wave** | 10,000 image impressions/month | 0.19 USD per 1,000 impressions, 0.38 USD for video |
For detailed pricing information, check [adrop.io/pricing](https://adrop.io/pricing).
***
## Payment Methods
Register a card for infrastructure cost payments.
* **Korean Cards**: Registered through TossPayments, supports KRW payment.
* **Global Cards**: Registered through Stripe, supports payment in all currencies.
The Stream plan can be used without a payment method. Wave plan or higher requires payment method registration to bill overage charges.
***
## Usage and Billing
You can check monthly usage and billing history.
* Usage and estimated billing amount are updated daily.
* Previous month's usage is billed on the 5th of each month. Credits are deducted first if available.
* PDF invoices can be downloaded.
Billable impressions include direct ad (campaign) and in-house ad impressions. Backfill ad impressions are not included in infrastructure costs.
Billing is based on UTC time.
***
## Refund Policy
Infrastructure costs are charges for resources already used and cannot be refunded directly from the Console. Please contact customer service if you were billed incorrectly or need a refund.
* Contact: [Contact Us](https://adrop.io/contactus)
***
## FAQ
Stream plan services are restricted. Wave plan bills overage charges.
You can download PDF invoices in Settings > Subscription and Billing tab.
Advertisers will see a "Service in Development" warning.
# Creating Audience Targeting
Source: https://docs.adrop.io/targeting/audience
Audience targeting displays ads to specific users based on user attributes (properties) and behavioral events. This guide explains how to create audience targeting using a local activity app as an example.
Create targeting options by combining user [properties](/targeting/property) and [events](/targeting/event).
***
## Creating Audience Targeting
### Step 1: Select Targeting Type
1. Click **Targeting** in the left menu, then select the **Audience** tab at the top.
2. Click the **Create Targeting** button.
**Auto-complete Targeting** (Age, Gender, Country, Language)
* Shown as cards with the **Auto** badge.
* Targeting options are automatically recommended.
* Targeting name cannot be changed.
**Custom Targeting**
* Configure targeting options directly using custom properties and events.
* Targeting name can be set freely.
### Step 2: Enter Targeting Name
(Custom targeting only) Enter the targeting name that will be displayed to advertisers.
* Use 2–36 characters, not counting leading or trailing spaces.
* Only Korean, English letters, numbers, spaces, and the symbols `~` `%` `&` `+` `-` are allowed. Other characters, such as Japanese, can't be used.
* The name must differ from the names of other targetings in the project, including both audience and context targeting.
### Step 3: Add Targeting Options
**For Auto-complete Targeting**
Click recommended targeting option cards to add them.
**For Custom Targeting**
1. Click the **+ Add Option** button.
2. Enter the targeting option name.
3. Set conditions.
* Click **+ Filter** to add a property or event condition
* Select an operator (is, contains, >, \<, etc.). The console shows operators by their English names and comparison symbols.
* Enter value
4. Combine multiple conditions if needed.
* **+ Filter**: Adds a condition to the current group. The first condition starts with **where**, and conditions are joined with **and** by default. Click **and** to change it to **or**.
* **+ Group**: Adds a new group. Groups are joined with **AND** by default. Click **AND** between groups to change it to **OR**.
5. Click the **Create Targeting Option** button.
* Targeting option names follow the same rules as targeting names and must be unique within the targeting.
* You can add up to 5,000 options per targeting. An option group counts as one option.
**Example 1: Property-based Targeting**
Combine `region` and `booking_count` properties to create "Regional User Tier" targeting.
| Targeting Option | Condition |
| - | - |
| **Seoul New User** | region is "Seoul" AND booking\_count \< 3 |
| **Seoul Active User** | region is "Seoul" AND booking\_count ≥ 3 AND booking\_count \< 10 |
| **Seoul Power User** | region is "Seoul" AND booking\_count ≥ 10 |
| **Busan New User** | region is "Busan" AND booking\_count \< 3 |
| **Busan Active User** | region is "Busan" AND booking\_count ≥ 3 AND booking\_count \< 10 |
| **Busan Power User** | region is "Busan" AND booking\_count ≥ 10 |
| **Jeju New User** | region is "Jeju" AND booking\_count \< 3 |
| **Jeju Active User** | region is "Jeju" AND booking\_count ≥ 3 AND booking\_count \< 10 |
| **Jeju Power User** | region is "Jeju" AND booking\_count ≥ 10 |
**Example 2: Combining Properties and Events**
Add event conditions for more precise targeting. For how event conditions work, see [Events](/targeting/event).
| Targeting Option | Condition |
| - | - |
| **Seoul Users with Purchase History** | region is "Seoul" AND did Purchase (Total Events ≥ 1, Last 30 Days) |
| **Cart Added but Not Purchased** | did Add to Cart (Total Events ≥ 1, Last 30 Days) AND did not do Purchase (Last 30 Days) |
| **VIP Purchasers** | membership is "VIP" AND did Purchase (Total Events ≥ 1, Last 30 Days) |
### Step 4: Save
Click the **Create Targeting** button to save.
***
## Option Groups
You can group multiple targeting options into one to offer to advertisers.
1. Click the created targeting card.
2. Check the targeting options you want to group.
3. Click the **Group options** button.
4. Enter the **Group name** and save.
5. Click the **Save** button.
* Advertisers can't select grouped sub-options individually.
* If you group options that live or scheduled campaigns have selected (**In Use**), those selections are removed from the campaigns' targeting, which can change who sees their ads. We recommend grouping after those campaigns end.
* Removing or deleting options from a group also changes the reach of campaigns that selected the group.
**Example: Grouping into a 'Coastal' Group**
Group the Busan and Jeju targeting options into a "Coastal" group.
| Option Group | Included Targeting Options |
| - | - |
| **Coastal New User** | Busan New User, Jeju New User |
| **Coastal Active User** | Busan Active User, Jeju Active User |
| **Coastal Power User** | Busan Power User, Jeju Power User |
Options displayed to advertisers after grouping:
* Seoul New User, Seoul Active User, Seoul Power User
* **Coastal New User**, **Coastal Active User**, **Coastal Power User**
***
## Targeting Duration
In audience targeting, you can set how long users remain in the audience after meeting targeting conditions. If a user's property data is newly collected and checked again within the duration and the user still meets the conditions, the duration restarts from that point.
* Duration is set for each audience targeting. Context targeting doesn't have a duration.
* You can set a whole number of days from 7 to 270. The default is 270 days.
* If you change the duration, the audience is recalculated at the next hourly audience update, and users who meet the conditions at that point get the new duration from then.
### Duration Example
* Target condition: Resides in Gangnam-gu, Seoul
* Duration: 30 days
* When a user first meets the Gangnam-gu Seoul condition, they're included in the audience for 30 days from that point.
* If the user's properties are collected again within 30 days and still meet the condition, the duration renews for another 30 days from that point.
* If the user's properties are collected again within 30 days but no longer meet the condition (for example, the user moved to another district), they're excluded from the audience even if time remains.
* If the duration isn't renewed within 30 days, they're automatically excluded from the audience when the 30 days end.
### Modifying Duration
1. Click **Targeting** in the left menu, then select the **Audience** tab at the top.
2. Click on an existing targeting or create new targeting and select options.
3. Modify the 'Duration' in the left section.
4. Click the **Save** button.
***
## Next Steps
Learn how to create content-based targeting
Learn how to connect targeting to ad products
***
## FAQ
More granular targeting options mean smaller audience sizes. If too small, advertisers may not select them or ad impressions may be insufficient. Find a balance between meaningful scale and precision for advertisers.
Use option groups. Even if individual options have small audiences, grouping related options into one provides meaningful scale to advertisers. For example, group "Busan New User" and "Jeju New User" to sell as "Coastal New User".
# Targeting Option Rules Detailed Guide
Source: https://docs.adrop.io/targeting/category-rules
When creating targeting options, you set conditions on properties or contexts. This guide explains available operators by data type and how to combine filters.
***
## Value Format by Data Type
Formats used when passing property values from the SDK.
| Type | Format | Example |
| - | - | - |
| **String** | Text | `"seoul"`, `"premium"`, `"user@email.com"` |
| **Number** | Integer or decimal | `0`, `123`, `-456`, `1234.56` |
| **Boolean** | true / false | `true`, `false` |
| **Date** | ISO 8601 (recommended) | `2024-01-18`, `2024-01-20T08:20:00` |
ISO 8601 format (`YYYY-MM-DD`) is recommended for dates. `YYYYMMDD` and timestamps (ms) are also supported.
***
## String Operators
Operators available for string type properties.
| Operator | Description | Example |
| - | - | - |
| **is** | Include users that match exactly | region is "Seoul" |
| **is not** | Exclude users that match exactly | region is not "Seoul" |
| **contains** | Include users that contain the value | email contains "gmail" |
| **does not contains** | Exclude users that contain the value | email does not contains "test" |
| **starts with** | Include users that start with the value | username starts with "admin" |
| **ends with** | Include users that end with the value | email ends with ".com" |
String operators are case-sensitive. "Seoul" and "seoul" are treated as different values.
The is and is not operators allow selecting multiple values. Example: region is "Seoul", "Busan", "Jeju"
For the default properties Country, Language, and Gender, only the is operator is available.
***
## Number Operators
Operators available for number type properties.
| Operator | Description | Example |
| - | - | - |
| **is** | Include users that match exactly | age is 25, 30, 35 |
| **is not** | Exclude users that match exactly | age is not 0 |
| **=** (equals) | Include users equal to value | trade\_count = 10 |
| **≠** (not equals) | Include users not equal to value | trade\_count ≠ 0 |
| **>** (greater than) | Include users greater than value | trade\_count > 5 |
| **≥** (greater than or equals) | Include users greater than or equal to value | trade\_count ≥ 5 |
| **\<** (less than) | Include users less than value | trade\_count \< 20 |
| **≤** (less than or equals) | Include users less than or equal to value | trade\_count ≤ 20 |
| **between** | Include users between two values | age between 20, 29 |
| **not between** | Exclude users between two values | age not between 20, 29 |
In the console, operators are shown in English regardless of the display language, and number comparison operators are shown as symbols (=, ≠, >, ≥, \<, ≤). The is and is not operators allow selecting multiple values. between / not between require two values.
***
## Boolean Operators
Operators available for boolean (true/false) type properties.
| Operator | Description | Example |
| - | - | - |
| **is True** | Include users with true value | is\_premium is True |
| **is False** | Include users with false value | is\_premium is False |
***
## Date Operators
Operators available for date type properties.
| Operator | Description | Example |
| - | - | - |
| **last** | Include users within past period | last\_login last 7 days |
| **before the last** | Include users before past period | last\_login before the last 30 days |
| **next** | Include users within next period | subscription\_end next 7 days |
| **after the next** | Include users after next period | subscription\_end after the next 30 days |
| **before** | Include users before specific date | created\_at before 2024-01-01 |
| **after** | Include users after specific date | created\_at after 2024-01-01 |
***
## Combining Targeting Option Rules
This section applies to audience targeting options. Context targeting options consist of a single is condition in which you enter context IDs.
You can set multiple conditions for a single targeting option. Use the following buttons in the condition editor.
* **+ Filter**: Adds a condition row to the group. The first row starts with **where**, and the following rows are connected with **and** by default. Click **and** to change it to **or**.
* **+ Group**: Adds a new group. Groups are connected with **AND** by default. Click the **AND** button between groups to change it to **OR**.
In the examples below, conditions in parentheses belong to the same group.
### AND Conditions
To include only users who satisfy all conditions, add the conditions to the same group with **+ Filter** and keep them connected with **and**.
**Example: Seoul Power Seller**
* region is "seoul" **and**
* trade\_count ≥ 20
→ Users in Seoul region with 20+ trades
### OR Conditions
To include users who satisfy any of the conditions, add a group for each condition with **+ Group**, then change the connection between the groups to **OR**.
**Example: Capital Region Users**
* (region is "seoul") **OR**
* (region is "gyeonggi")
→ Users in Seoul or Gyeonggi region
### Complex Conditions
Use AND and OR together to create complex conditions.
**Example: Capital Region Power Sellers**
* (region is "seoul" **and** trade\_count ≥ 20) **OR**
* (region is "gyeonggi" **and** trade\_count ≥ 20)
→ Seoul power sellers or Gyeonggi power sellers
***
## Context Targeting Operators
In context targeting, only the **is** operator is available for context IDs. In the console, the condition field is shown as **Category ID**.
| Operator | Description | Example |
| - | - | - |
| **is** | Matches context ID exactly | Category ID is "phone", "laptop", "tablet" |
When selecting multiple context IDs with the is operator, ads display if any of those IDs match.
* **Excluding specific contexts**: Instead of an `is not` operator, create a targeting option with the context IDs you want to exclude. Then set that option as **Required Exclude** on the ad product, or advertisers can exclude it when booking a campaign. For details, see [Selling Targeting](/targeting/sell).
* **Partial matching**: Operators such as `contains` are not supported. Pass more specific context IDs from the SDK, and enter every value you want to match with **is**.
***
## Next Steps
Learn how advertisers select targeting to book campaigns
***
## FAQ
Entering exactly as passed from the SDK is critical. Especially for strings, which are case-sensitive—"Seoul" and "seoul" are treated as different values. Confirm actual passed values with your development team.
Useful for targeting user states that change over time. For example, "active users who logged in within the last 7 days", "users with subscription expiring within 30 days", "long-term members who joined over 1 year ago".
Use AND when all conditions must be satisfied simultaneously. Use OR when any one of multiple conditions can be satisfied. Conditions added with **+ Filter** and groups added with **+ Group** are connected with AND by default; click the **and** or **AND** button to change it to OR. For example, "Seoul power seller" is region=Seoul AND trade\_count≥20, while "Capital region users" is region=Seoul OR region=Gyeonggi.
# Creating Context Targeting
Source: https://docs.adrop.io/targeting/context
Context targeting displays ads on specific pages or content based on content attributes. This guide explains how to create context targeting using a local activity app as an example.
Configure targeting categories based on the activity category the user is viewing.
***
## What is Context?
**Context** is contextual information about the page the user is currently viewing. Any data that can distinguish pages—such as product categories, article classifications, or search keywords—can be context.
Context is passed via SDK during ad requests, and targeting categories are created by combining passed contexts.
***
## Passing Context ID
Context IDs are defined by the publisher. If you already have a content classification system in your service, you can use it directly. When requesting ads via SDK with a context ID, ads matching that context are displayed.
Context targeting applies only to banner and native ad requests that include a Context ID (banner and native ads in the [Android](/sdk/android/targeting#contextual-targeting) and [iOS](/sdk/ios/targeting#contextual-targeting) SDKs, the [Web SDK](/sdk/web/targeting#contextual-targeting), and the [REST API](/sdk/rest-api/targeting#context-targeting)). Interstitial, rewarded, popup, and splash ads, as well as the Flutter and React Native SDKs, do not send a Context ID. If an ad product that contains these placements uses context targeting, campaigns that include context options are not shown in those placements, and context exclusions are not applied there.
Context IDs are case-sensitive and must exactly match a value entered in a context targeting option's condition in the console. Do not use commas (,) in Context IDs, because a comma separates multiple values both in the console and in an ad request. When requesting ads, pass the value exactly as entered in the console, without leading or trailing spaces.
If you don't pass a Context ID, or pass a value that isn't registered in the console, no error occurs. Only campaigns with context targeting are excluded, and other ads can still be served.
### Passing Multiple Context IDs
A placement can send more than one Context ID in a single ad request. Separate them with commas.
```http theme={null}
GET https://api-v2.adrop.io/request?unit=YOUR_UNIT_ID&contextId=hiking,outdoor,camping
```
| Rule | Behavior |
| - | - |
| Separator | A comma (`,`). Spaces around each ID are trimmed, and empty entries are dropped. |
| Duplicates | Removed. |
| Maximum | The first 5 IDs are used, and the rest are ignored. |
| Matching | The targeting options matched by each ID are combined. A campaign is served when **any** of the IDs matches an option the campaign includes. |
| Exclusion | Exclusion takes precedence. If any of the IDs matches an option the campaign excludes, the campaign isn't served, even when another ID matches an included option. |
Repeating the parameter, as in `?contextId=hiking&contextId=outdoor`, works the same way as the comma-separated form.
### Example: Local Activity App
Assuming a local activity app passes the category of the activity detail page the user is viewing as context.
| Activity Page | Context ID |
| - | - |
| **Bukhansan Hiking Meetup** | hiking |
| **Surfing Lessons** | surfing |
| **Han River Kayaking** | outdoor |
| **Glamping Party** | camping |
| **Italian Cooking Class** | cooking |
| **Traditional Liquor Tasting** | tasting |
| **Jeju Restaurant Tour** | food |
| **Pottery Workshop** | craft |
| **Contemporary Art Exhibition Tour** | exhibition |
| **Musical Theater** | culture |
| **Flower Art Class** | art |
| **Yoga Class** | yoga |
| **Meditation Workshop** | meditation |
| **Spa Day** | spa |
***
## Creating Context Targeting
### Step 1: Create Targeting
1. Click **Targeting** in the left menu, then select the **Context** tab at the top.
2. Click the **Create Targeting** button. If you don't have any context targeting yet, click the **Create** button on the empty screen.
### Step 2: Enter Targeting Name
Enter the targeting name that will be displayed to advertisers.
### Step 3: Add Targeting Options
1. Click the **+ Add Option** button.
2. For **Type**, select **Option**. To offer several options as one, select **Option group** instead (see [Option Groups](#option-groups)).
3. Enter the **Option name**.
4. In **Context ID**, enter the value your placement sends when it requests an ad. Separate multiple IDs with commas.
5. Click the **Add** button.
**Example: Configuring targeting options for a local activity app**
Combine Context IDs to build an "Activity Category" targeting.
| Targeting Option | Context ID |
| - | - |
| **Outdoor** | outdoor, camping, hiking, surfing |
| **Food** | food, cooking, tasting |
| **Culture** | culture, exhibition, concert |
| **Art** | art, craft, design |
| **Wellness** | wellness, yoga, meditation, spa |
Targeting names and option names can contain only Korean, English letters, numbers, spaces, and the symbols `~ % & + -`.
### Step 4: Save
Click the **Create Targeting** button to save.
***
## Option Groups
You can group several targeting options into one and offer them to advertisers. A group is a folder of options, and advertisers select the whole group or the options inside it.
### Creating an Option Group
1. Click the **+ Add Option** button and select **Option group** for **Type**.
2. Enter the **Group name**.
3. Enter the **Context ID**. You can leave it empty if the group has sub-options.
4. Set the **Show sub-options to advertisers** switch.
5. Click the **Add** button.
To group options you already created, check the targeting options and click the **Group options** button.
### Showing Sub-options
The **Show sub-options to advertisers** switch only changes what advertisers can select. It doesn't affect ad serving.
| Switch | What advertisers see |
| - | - |
| On | They can pick the whole group, or pick the options inside it one by one |
| Off | Only the group name is shown, and sub-options can't be selected individually |
### Matching Scope
A group matches both its own Context IDs and the Context IDs of its sub-options.
### Limits
* An option group needs at least one sub-option or one Context ID of its own.
* A group can't contain another group. Sub-options are always regular options.
* An option group's own Context IDs can't overlap with the own Context IDs of another option group in the same project.
* You can't change an option's type after you create it, and you can't choose a type when adding a sub-option.
### Editing, Ungrouping, and Deleting
Use the menu (⋮) on a targeting option row to select **Edit group**, **Ungroup**, or **Delete**. In the edit group window, click **Complete** to save.
* **Ungroup**: The group's Context IDs are deleted with it, and its sub-options stay as top-level options.
* **Delete**: Deleting a group also deletes its sub-options. Move them out of the group first to keep them.
* If a running campaign uses the group, ungrouping or deleting it can change that campaign's reach or let it serve without this targeting condition. We recommend doing this after the campaign ends.
You can also create and change option groups with the [Context Targeting API](/integrations/context-targeting/option-groups). Note that deleting a group through the API promotes its sub-options to top-level options, while deleting it in the console also deletes them.
***
## Next Steps
Learn how to connect to ad products and set required targeting
Manage targeting options and Context IDs automatically from your server
***
## FAQ
It's best to use your existing content classification system as-is. You don't need to create new ones—just pass existing category codes or tags as context IDs.
Yes, context targeting is based on the content of the page the user is currently viewing. Since no user information is needed, it applies equally to non-logged-in users.
Effective for services with clear content classification. If page context is distinct—like news app article categories, commerce product categories, or real estate app listing types—you can provide valuable targeting to advertisers.
# Events
Source: https://docs.adrop.io/targeting/event
Events are user behavioral data such as purchases and adding to cart. By using events, you can add behavior-based conditions to audience targeting.
***
## What are Events?
**Events** are behavioral data representing actions users perform in apps or on the web. They record specific user actions such as purchases, adding to cart, sign-ups, and inquiry completions.
While **properties** represent user attribute information (age, gender, membership tier, etc.), **events** represent actions that users have actually performed.
| Type | Description | Examples |
| - | - | - |
| **Properties** | User attribute information | Age, gender, membership tier, region |
| **Events** | User behavioral data | Purchase, add to cart, sign-up, inquiry completion |
By using events, you can add behavior-based targeting conditions such as "users with purchase history," enabling more precise audience groups compared to using properties alone.
***
## Event Categories
Events are classified into three categories based on their nature.
| Category | Description | Preset Events |
| - | - | - |
| **Common User Actions** | Basic in-service actions | App Open, Sign Up, Push Click, Page View, Element Click |
| **Commerce & Payments** | Actions at each stage of the purchase funnel | View Item, Add to Wishlist, Add to Cart, Begin Checkout, Purchase |
| **Lead Collection & Inquiries** | Actions that show inquiry intent | View Content, Begin Lead Form, Submit Lead |
***
## Event Presets
Ad Control provides frequently used events as system presets by default.
* Preset events can be used immediately without separate registration.
* Preset event names and parameters cannot be modified.
* Only preset events can be selected as targeting conditions.
The preset event list can be viewed on the Events page in the console.
***
## Using the Events Page
Click **Event** in the console left menu to navigate to the Events page.
### Event List
The Events page lists the preset events by category. For each event, you can see its name, event and user counts over the last 30 days, when it was last collected, and its collection status at a glance.
### Event Details
Click an event to open the **Event Detail** modal. The detail modal shows the following information:
* **Status**, **Events (30d)**, **Users (30d)**, **Last Collected**: Collection status, event and user counts over the last 30 days, and when the event was last collected
* **Parameters**: Keys and data types of the parameters included in the event. Click **Integration Guide** to see the [SDK Integration](#sdk-integration) guide.
* **Data Collection Log**: Up to 100 event records collected in the last 24 hours
After integrating the SDK, check **Last Collected** and **Data Collection Log** to confirm that events are being collected.
***
## Using Events in Audience Targeting
When creating audience targeting, you can add events as conditions in addition to properties.
### Adding Event Conditions
1. Create targeting in **Targeting → Audience**.
2. Click **+ Filter** in condition settings.
3. Select the **Event** tab.
4. Select the event to use.
5. Set the details of the condition, and add conditions on event parameters if needed.
An event condition is set in this order: **who** \[**did** / **did not do**] \[event] \[aggregation] \[operator and value] \[period].
| Item | Description |
| - | - |
| **did / did not do** | Choose whether to target users who performed the event (did) or users who didn't (did not do) |
| **Aggregation** | Choose from **Total Events**, **Sum**, **Average**, **Minimum**, **Maximum**, and **Distinct Count**. Except for Total Events, you also select the event parameter to aggregate |
| **Operator and value** | Compare the aggregated result with a value using =, ≠, >, ≥, \<, or ≤ |
| **Period** | The last N hours, days, weeks, or months (**Last**). The default is 30 days, and the maximum is 90 days |
If you select **did not do**, you don't set the aggregation, operator, or value.
* You can add up to 5 event conditions per group.
* All event conditions in a targeting option must be connected the same way: all **and** or all **or**.
* The condition period is separate from the event data retention period (18 months).
### Combining Properties and Events
Combining properties and events together enables more precise target groups.
**Example: Commerce App Targeting**
| Targeting Option | Condition |
| - | - |
| **Seoul Users with Purchase History** | region is "Seoul" AND did Purchase (Total Events ≥ 1, Last 30 Days) |
| **Cart Added but Not Purchased** | did Add to Cart (Total Events ≥ 1, Last 30 Days) AND did not do Purchase (Last 30 Days) |
| **VIP Purchasers** | membership is "VIP" AND did Purchase (Total Events ≥ 1, Last 30 Days) |
***
## Technical Specifications
| Item | Details |
| - | - |
| **Data Collection** | About every 10 minutes |
| **Targeting (Audience) Update** | Hourly |
| **Data Retention Period** | 18 months |
***
## SDK Integration
To send events from your app, integrate event tracking in the SDK.
Set up event tracking in Android
Set up event tracking in iOS
***
## Next Steps
Create targeting by combining properties and events
***
## FAQ
Properties are user attribute information (age, gender, membership tier, etc.), while events are user behavioral data (purchases, add to cart, etc.). Properties indicate "who the user is," and events indicate "what they did."
Preset event names and parameters cannot be modified. They are standard events provided by the system and should be used as-is for consistent data collection and analysis.
Events sent from the SDK are collected about every 10 minutes. They are applied to targeting conditions at the hourly audience update, so it can take more than an hour.
Event data is retained for 18 months. Data older than 18 months is automatically deleted.
# Managing Targeting
Source: https://docs.adrop.io/targeting/manage
Learn how to edit, delete, and manage the status of created targeting and targeting options.
***
## Viewing Targeting List
1. Click **Targeting** in the left menu, then select the **Audience** or **Context** tab at the top.
2. Click a targeting card to go to the detail screen.
***
## Editing Targeting Display Order
Change the order in which targeting is displayed to advertisers when booking campaigns.
1. Click **Targeting** in the left menu, then select the **Audience** or **Context** tab at the top.
2. Click the **Display Order** button.
3. Drag targeting items to change the order.
4. Click the **Save** button.
***
## Editing Targeting
### Changing Targeting Name
1. Click a targeting card to go to the detail screen.
2. Enter a new name and click **Save**.
Auto-complete targeting (Age, Gender, Country, Language) names cannot be changed.
***
## Deleting Targeting
1. In the targeting list, click the menu (⋯) on the card of the targeting you want to delete.
2. Select **Delete Targeting**.
3. Click **Delete** in the confirmation popup.
Only members with **Owner** permission can delete targeting.
Targeting cannot be deleted if any of its options are In Use.
Deleting targeting removes it from connected ad products.
***
## Managing Targeting Options
### Targeting Option Status
| Status | Description |
| - | - |
| **In Use** | Approved live or scheduled campaigns have selected this option as include or exclude. It changes to Available after those campaigns end. |
| **Available** | Can be connected to ad products and sold |
| **Unavailable** | The conditions need to be fixed (for example, a property used in the conditions was deleted or its data type was changed). An option group shows this status if any option in it does. |
| **Draft** | Option not yet saved |
Only Available or In Use options can be connected to ad products.
### Adding Targeting Options
1. Click the **+ Add Option** button on the targeting detail screen.
2. Set the targeting option name and conditions.
3. Click the **Create Targeting Option** button.
4. Click **Save** at the bottom of the screen and confirm the changes in the confirmation window.
### Editing Targeting Options
For audience targeting, the updated audience size shown in the console is reflected within up to 1 hour.
1. Click the targeting option to edit.
2. Modify conditions and click **Save**.
### Grouping Options
For audience targeting, the updated audience size shown in the console is reflected within up to 1 hour.
1. Check the targeting options you want to group.
2. Click **Group options**.
3. Edit the group name and click **Save**.
4. Click **Save** at the bottom of the screen and confirm the changes in the confirmation window.
If you group options that live or scheduled campaigns have selected, those selections are removed from the campaigns' targeting, which can change who sees their ads. We recommend grouping after those campaigns end. Removing or deleting options from a group also changes the reach of campaigns that selected the group.
### Deleting Targeting Options
1. On the targeting detail screen, click the menu (⋮) on the row of the option you want to delete.
2. Select **Delete option**.
3. Click **Save** at the bottom of the screen.
4. In the **Edit targeting?** window, review the affected campaigns and products. If any are listed, select the confirmation checkbox, then click **Save**.
* Only members with **Owner** permission can delete targeting options.
* Options are not deleted until you save. If you leave the screen without saving, the option is not deleted.
**Important notes when editing, deleting, or grouping targeting options**
* **Impact on live campaigns**: Structural changes such as deleting, grouping, or renaming options are applied to live campaigns as soon as you save, which may affect performance.
* **When changes take effect**: Condition changes and new options in audience targeting are applied after the next hourly audience update, so this can take more than an hour. In context targeting, Context ID changes and adding or grouping options can also take some time to be reflected in ad serving.
* **Deleting In Use options**: You can delete options that are In Use. When you do, they are immediately removed from the targeting of live campaigns. If all options a campaign included in that targeting are deleted, the campaign is served without that targeting condition, so check the affected campaigns before deleting.
* **Ad Products**: Product targeting settings may be affected, so be sure to review the product's targeting settings.
***
## Next Steps
Learn more about setting conditions
***
## FAQ
You cannot delete targeting if any of its options are In Use (selected by approved live or scheduled campaigns). Delete it after the related campaigns end. Individual targeting options can be deleted even when they are In Use, so check the impact on campaigns before deleting them.
Place frequently used targeting or those with high revenue contribution at the top. Advertisers see these first when booking campaigns, increasing selection likelihood.
# What is Targeting?
Source: https://docs.adrop.io/targeting/overview
Set up targeting to deliver customized ads to the users that advertisers want to reach. Well-designed targeting improves ad effectiveness and provides justification for premium pricing.
***
## What is Targeting?
**Targeting** is a feature that allows ads to be displayed only to specific users or on specific content.
For example, you can show baby product ads only to parents in their 20s and 30s, or show sports brand ads only on sports article pages. Through targeting, advertisers can precisely reach their desired customers, and publishers can create premium ad products by leveraging their data.
***
## Why is Targeting Important?
### It's Time to Monetize Your Data
The data that publishers possess is a valuable asset in itself. You can increase advertising revenue by turning existing data such as member information, behavioral data, and content classification into targeting products.
| Data You Have | Monetization Potential |
| - | - |
| **Member Information (Age, Gender, Region)** | Demographic targeting products |
| **Behavioral Data (Purchase History, Interests)** | Interest-based targeting products |
| **Behavioral Events (Purchase, Add to Cart, Sign-up)** | Behavior-based targeting products |
| **Financial Data (Income Level, Asset Size)** | High-income targeting products |
| **Content Classification (Category, Keywords)** | Context targeting products |
| **Membership Tier** | Premium audience products |
### The Difference Targeting Makes
**Selling Without Targeting:**
* All ads are sold at the same CPM.
* Advertisers don't know if they reached their desired customers.
* The value of the publisher's data is not reflected in pricing.
**Leveraging Targeting:**
* Apply premium rates to premium audiences for **higher pricing**.
* Provide advertisers with **clear targeting options** to improve ad effectiveness.
* Turn your data into **differentiated ad products** to gain competitive advantage.
### Real-World Examples
Created "Parents within 6 months postpartum" targeting and sells to baby formula and diaper brands at a 40% premium over base CPM.
By providing context targeting for finance, real estate, and automotive categories, they achieved high repurchase rates from advertisers in related industries.
***
## Audience Targeting
Display ads to specific users based on user attribute data.
### Data Source
Uses **User Properties and Events**. Properties are user attribute information, while events are user behavioral data such as purchases and adding to cart. Basic fields such as country, language, gender, and age are provided, and publishers can add custom fields as needed.
### Hierarchy Structure
Targeting is created in the following order.
Attribute data such as age, gender, interests and behavioral data such as purchases, adding to cart
Segments created by applying conditions to properties
Collection of related targeting options
**Properties** are source data such as age, gender, interests, region of residence, and membership tier.
**Events** are behavioral data representing actions users have performed, such as purchases, adding to cart, sign-ups, and inquiry completions.
**Targeting options** are segments created by applying conditions to properties and events. For example, "Women in their 20s" is a targeting option combining the conditions of age 20-29 and female gender. "Users with Purchase History" is a targeting option using the Purchase event condition.
**Targeting** is a collection of related targeting options. The "Demographics" targeting includes targeting options like women in their 20s, men in their 20s, women in their 30s, and men in their 30s. Advertisers select the options they want within the targeting.
**Option groups** let you group multiple targeting options into one. If you group women in their 20s and men in their 20s, they become a single targeting option called "20s". Advertisers can't select grouped sub-options individually, so they can only select the "20s" option.
### Example: Demographics Targeting
**Before grouping options**
Advertiser selection options: Women in their 20s, Men in their 20s, Women in their 30s, Men in their 30s
**After grouping options**
* 20s = Women in their 20s + Men in their 20s
* 30s = Women in their 30s + Men in their 30s
Advertiser selection options: 20s, 30s
### Usage Scenarios
| Service | Property / Event | Category Examples |
| - | - | - |
| **Parenting App** | Months postpartum | Newborn, Weaning period, Toddler |
| **Finance App** | Asset size | Under 100M, Over 100M, Over 1B |
| **Commerce** | Membership tier | Regular, Premium, VIP |
| **Commerce** | Purchase event | Purchased, Not purchased, Repeat purchaser |
| **OTT** | Viewing genre | Drama, Variety, Movies |
| **News App** | Interest area | Politics, Economy, Sports, Entertainment |
| **Lead Gen App** | Inquiry event | Inquiry completed, Not completed |
***
## Context Targeting
Display ads on specific pages or content based on content attributes.
### Data Source
Uses **Context ID** passed during ad requests. Context ID is a value representing the context of the page the user is currently viewing, defined by the publisher and passed through the SDK.
For example, if a user is viewing a sneaker product page in a commerce app, you can pass a context ID like "shoes" or "sports\_shoes".
Context targeting applies only to banner and native ad requests that include a Context ID (banner and native ads in the Android and iOS SDKs, the Web SDK, and the REST API). Interstitial, rewarded, popup, and splash ads, as well as the Flutter and React Native SDKs, do not send a Context ID. If an ad product that contains these placements uses context targeting, campaigns that include context options are not shown in those placements, and context exclusions are not applied there.
### Hierarchy Structure
Targeting is created in the following order.
Page context information defined by the publisher
Segments combining context IDs
Collection of related targeting options
**Context** is page context information defined by the publisher. You can freely define product categories, search keywords, page types, etc.
**Targeting options** are segments created by combining specific context IDs. For example, the "Shoes" targeting option can be configured to include context IDs such as shoes, sneakers, boots, etc.
**Targeting** is a collection of related targeting options. The "Product Category" targeting includes options like shoes, clothing, bags, and accessories.
**Option groups** let you group multiple targeting options into one. If you group clothing and accessories, they become a single option called "Fashion Apparel". With **Show sub-options to advertisers** turned on, advertisers can also select each sub-option individually.
### Example: Commerce App Product Category Targeting
**Before grouping options**
Advertiser selection options: Shoes, Clothing, Bags, Accessories
**After grouping options**
* Fashion Apparel = Clothing + Accessories
Advertiser selection options: Shoes, Bags, Fashion Apparel
### Usage Scenarios
| Service | Context | Category Examples |
| - | - | - |
| **Commerce** | Product category | Shoes, Clothing, Bags, Accessories |
| **News App** | Article category | Politics, Economy, Sports, Entertainment |
| **Real Estate App** | Property type | Apartment, Officetel, Villa, Studio |
| **Recipe App** | Cuisine type | Korean, Chinese, Western, Japanese |
| **Travel App** | Destination | Domestic, Japan, Southeast Asia, Europe |
***
## Connecting Targeting to Ad Products
Set how to connect targeting when creating ad products.
### Visible Targeting
Targeting options that advertisers can select during campaign booking. Only options selected here are visible to advertisers.
Example: If you set only 20s and 30s as visible targeting from the "Demographics" targeting options of 20s, 30s, and 40s, advertisers can only select 20s and 30s.
### Required Targeting
Targeting options that are automatically applied to every campaign booked with the product. Advertisers cannot remove them.
Required targeting has two settings: **Include** and **Exclude**.
| Setting | Description | Example |
| - | - | - |
| **Include** | Options that are automatically included in every campaign at booking. Advertisers cannot deselect them. | "20s" required include → Every campaign for this product includes 20s |
| **Exclude** | Options where ads will not be displayed | "40s" exclude setting → Ads won't be shown to users in their 40s |
If an advertiser also selects optional options in the same targeting, they are combined with the Required Include options using OR, which widens the audience. To serve only to specific options, do not expose other options of the same targeting for advertisers to select.
If a context targeting has a Required Include or Required Exclude setting, no other context targeting can be exposed in the same ad product. Advertisers can choose include or exclude options in only one context targeting.
### Premium Rate Setting
You can set a premium rate for each targeting to apply premium pricing. If a campaign includes or excludes any option of that targeting, the premium rate is added to the base price once. This also applies to Required Include and Required Exclude options and to options the advertiser excludes, so the premium rate of a targeting with Required Include or Required Exclude options applies to every campaign of the product.
| Item | Description |
| - | - |
| **Setting Range** | 0% \~ 200% |
| **Application Method** | Base price × (1 + premium rate) |
| **Cumulative Application** | Premium rates of different targetings are summed. Selecting several options in the same targeting applies its premium rate only once |
**Example**: Base price 10,000 won, 20% premium rate → 12,000 won
***
## Precautions
* **Impact on live campaigns**: Structural changes such as deleting, grouping, or renaming options are applied to live campaigns as soon as you save, which may affect performance.
* **When changes take effect**: Condition changes and new options in audience targeting are applied after the next hourly audience update, so this can take more than an hour. In context targeting, Context ID changes and adding or grouping options can also take some time to be reflected in ad serving.
* **Deleting In Use options**: You can delete options that are In Use. When you do, they are immediately removed from the targeting of live campaigns. If all options a campaign included in that targeting are deleted, the campaign is served without that targeting condition, so check the affected campaigns before deleting.
* **Impact of modifying targeting included in products**: Targeting settings may be affected, so you must recheck the targeting settings of the product.
* **Audience size update delay**: For audience targeting, the audience size shown in the console after a change is updated up to 1 hour later.
***
## Table of Contents
1. ⚡ [Quickstart: Selling Your First Targeting](/targeting/quickstart)
2. [Properties](/targeting/property)
3. [Events](/targeting/event)
4. [Creating Audience Targeting](/targeting/audience)
5) [Creating Context Targeting](/targeting/context)
6) [Selling Targeting](/targeting/sell)
7) [Managing Targeting](/targeting/manage)
8) [Category Rules Detailed Guide](/targeting/category-rules)
***
## FAQ
You can generate higher revenue from the same ad inventory. Apply premium rates to premium audiences and provide advertisers with clear targeting options to improve ad effectiveness. Turn your data into differentiated ad products to gain competitive advantage.
Evaluate based on "Can this data help advertisers find their desired customers?" For example, "Months postpartum" for parenting apps is valuable targeting for baby formula and diaper brands, and "Asset size" for finance apps is valuable targeting for premium financial product advertisers. Consider whether it's data advertisers would willingly pay a premium for.
Choose based on the type of data you have. If you have rich user attribute data like member information and behavioral data, audience targeting is suitable. If you have a well-structured content classification system, context targeting is effective.
Consider the scarcity of the audience and ad effectiveness. Valuable segments like "High-income earners" and "VIP members" can have higher premium rates. You can also adjust based on market response.
# Properties
Source: https://docs.adrop.io/targeting/property
Properties are user attribute information such as age, gender, interests, and membership tier. Learn how to register and manage properties for audience targeting.
***
## What are Properties?
**Properties** are user attribute information. Any data that can distinguish users—such as age, gender, interests, or membership tier—can be a property.
Properties are collected via SDK, and targeting options are created by combining collected properties.
***
## Default Properties
Properties provided by default in Ad Control.
| Property | Description | Collection Method | Data Format |
| - | - | - | - |
| **Country** | User's country information | Auto-collected | ISO country code 2 digits (KR, US, JP) |
| **Language** | User's language setting | Auto-collected | ISO language code (ko, en, ja) |
| **Age** | User's age | Requires birthday from SDK | YYYY, YYYYMM, YYYYMMDD format |
| **Gender** | User's gender | Requires SDK pass | M(Male), F(Female), O(Other), U(Unknown) |
Country and Language are auto-collected from device locale settings. Age and Gender must be passed directly from the SDK.
***
## Custom Properties
You can add custom properties beyond defaults to fit your service.
### Example: Local Activity App
Assuming a local activity app collects user activity region and booking count.
| Property | Type | Description | Example Values |
| - | - | - | - |
| **`region`** | String | User's activity region | Seoul, Busan, Jeju |
| **`booking_count`** | Number | Cumulative booking count | 0, 5, 20 |
String values are case-sensitive. Enter exactly as passed from the SDK.
Combining these two properties allows creating various targeting options.
### Adding Custom Properties
1. Click **Property** in the left menu.
2. Click the **Property Detail** button.
3. Click **+ Add Property**, then enter the property name and select a data type.
4. Click the **Save** button.
You can register up to 50 properties, including the default properties.
### Property Naming Rules
* 3-64 characters using only lowercase English letters, numbers, and underscores (\_)
* Examples: `membership_level`, `asset_size`, `baby_months`
### Data Types
| Type | Description | Examples |
| - | - | - |
| **String** | Text value | "premium", "gold", "drama" |
| **Number** | Integer or decimal | 25, 100000000, 3.5 |
| **Boolean** | True/false | true, false |
| **Date** | Date value | 2024-01-15 |
Array types are not supported. To use multiple values, split them into separate properties (for example, Boolean properties such as `interest_tech` and `interest_gaming`) or convert them into a single String value.
You can't change the data type of, or delete, a property used by an option that live or scheduled campaigns have selected (**In Use**). If the property is used only by options that aren't In Use, you can change or delete it after checking the affected targetings, but those options become **Unavailable** and you need to set their conditions again. Default properties can't be deleted, and their data types can't be changed.
***
## Next Steps
Learn about behavior-based targeting using events
Create targeting by combining properties and events
***
## FAQ
Yes, you can start right away with default properties like Country, Language, Age, and Gender. Adding custom properties unique to your service (membership tier, purchase history, interests, etc.) later enables differentiated premium targeting versus competitors.
Properties are user attribute information (age, gender, membership tier, etc.), while events are user behavioral data (purchases, add to cart, etc.). Properties indicate "who the user is," and events indicate "what they did." Both can be used as conditions in audience targeting.
# Quickstart: Selling Your First Targeting
Source: https://docs.adrop.io/targeting/quickstart
This guide shows you how to create age targeting, start selling, and connect it to ad products.
> ⏱️ Time required: About 10 minutes
***
## Before You Begin
* SDK integration completed
* Age property being collected (birthday information must be passed from SDK)
* At least 1 ad product created
To use age targeting, user birthday information must be passed from the SDK.
***
## Step-by-Step Guide
Log in to [Ad Control Console](https://console.adrop.io).
1. Click **Targeting** in the left menu, then select the **Audience** tab at the top.
2. Click the **Create Targeting** button. If the tab has no targeting yet, click the **Create** button instead.
3. Select the **Age** card (with the **Auto** badge) from the targeting template list.
Age targeting supports auto-complete. Targeting options are automatically recommended.
Auto-complete targeting names cannot be changed.
Select targeting options to sell from the auto-recommended options.
1. Click the targeting option card to add.
2. Check the option name and conditions.
3. Click the **Create Targeting Option** button.
4. Click the **Create Targeting** button to save.
You can also create targeting options manually. Click the + Add Option button.
Connect the targeting to ad products.
1. Click **Ad Products** in the left menu.
2. Select the ad product to connect targeting.
3. Click the **\[...]** button in the top right of the detail page, then select **Edit**.
4. Click **Next** until you reach the **Targeting** step.
5. In the **Targeting to display on product** section, select the **Audience** tab and turn on the switch for the targeting to connect.
6. In the window that opens, select the targeting options to connect and click **Save**.
7. Click **Save** at the bottom of the screen to save the product.
An ad product can display either audience or context targeting. If you switch tabs, the targeting selected in the other tab is cleared.
***
## Complete
Now advertisers can select this targeting when booking campaigns.
***
## Next Steps
**Learn More**
Learn how to create targeting with properties and events.
Learn how to create content-based targeting.
**Go to Next Quickstart**
See how advertisers select targeting and book campaigns.
***
## FAQ
No. You need to connect the targeting to ad products so advertisers can select it when booking campaigns.
Yes, you can set different targeting options to expose for each ad product. You can connect high-value targeting only to premium ad products, or selectively provide targeting that matches the ad product characteristics.
Auto-complete targeting (age, gender, country, language) allows quick setup as targeting options are automatically recommended. Custom targeting allows you to create differentiated products by configuring targeting options directly using service-specific properties.
# Selling Targeting
Source: https://docs.adrop.io/targeting/sell
Learn how to connect created targeting to ad products and sell to advertisers.
***
## Connecting to Ad Products
Connect targeting options that are Available or In Use to ad products. Only connected targeting can be selected by advertisers when booking campaigns.
### Selecting Targeting Type
Only **one** of Audience Targeting or Context Targeting can be connected to a single ad product.
| Targeting Type | Description | Examples |
| - | - | - |
| **Audience Targeting** | User attribute-based | Age, Gender, Membership tier |
| **Context Targeting** | Content attribute-based | Product category, Article classification |
### Setting Exposed Targeting
Specify targeting options advertisers can select when booking campaigns.
1. Click **Ad Products** in the left menu.
2. Select the ad product to connect targeting.
3. Click the **\[...]** button in the top right of the detail page, then select **Edit**.
4. Click **Next** until you reach the **Targeting** step, then go to the **Targeting to display on product** section.
5. Select either the **Audience** or **Context** tab. If you switch tabs, the targeting you selected in the other tab is cleared.
6. Click the switch for the targeting to expose.
7. Select targeting options, choose optional/required status, and click **Save** in the window.
8. Click the **Save** button at the bottom of the screen.
Only targeting options that are Available or In Use can be selected.
### Targeting Selection Methods
Specify how advertisers select targeting.
| Method | Description |
| - | - |
| **Free Selection** | Advertisers freely select from exposed targeting options and choose whether to include or exclude each option they select. |
| **Required Include** | Publisher-specified targeting options are automatically selected. Advertisers cannot deselect. |
| **Required Exclude** | Ads won't display to users in publisher-specified targeting options. |
**Setting Required Include**
1. In the **Targeting to display on product** section, click the switch for the targeting to expose.
2. Select targeting options, then click the Include button on the right.
3. Click the **Save** button.
**Setting Required Exclude**
1. In the **Targeting to display on product** section, click the switch for the targeting to expose.
2. Select targeting options, then click the Exclude button on the right.
3. Click the **Save** button.
***
## Targeting Combination Rules
When advertisers select multiple targeting options when booking campaigns, the following rules apply.
| Type | Rule | Description |
| - | - | - |
| **Between different targeting** | **AND** | Must satisfy all targeting conditions for display |
| **Between targeting options within same targeting** | **OR** | Display if any included targeting option matches |
| **Excluded targeting options** | **Exclusion first** | Not displayed to users who match any option excluded by the advertiser or set as Required Exclude, even if other conditions are met |
| **Between context targetings** | **One only** | Advertisers can choose include or exclude options in only one context targeting |
| **Premium rates** | **Cumulative** | Each targeting with included or excluded options, including Required Include and Required Exclude, adds its premium rate once. Premium rates of different targetings are summed |
### Example
When an advertiser selects targeting as follows:
| Targeting | Selected Targeting Options | Premium Rate |
| - | - | - |
| **Age** | 20s, 30s | 10% |
| **Region** | Seoul | 20% |
**Applied Result**
* **Display Condition**: (20s OR 30s) AND Seoul → Display to people in their 20s or 30s living in Seoul
* **Price Calculation**: \$10 + 10% + 20% = \$13 (Based on base CPM \$10)
***
## Important Notes
* **Cannot use Audience and Context targeting together**: Both types cannot be applied to a single ad product.
* **Context targeting with Required Include/Exclude**: If a context targeting has a Required Include or Required Exclude setting, no other context targeting can be exposed in the same ad product. Advertisers can also choose include or exclude options in only one context targeting.
Context targeting applies only to banner and native ad requests that include a Context ID (banner and native ads in the Android and iOS SDKs, the Web SDK, and the REST API). Interstitial, rewarded, popup, and splash ads, as well as the Flutter and React Native SDKs, do not send a Context ID. If an ad product that contains these placements uses context targeting, campaigns that include context options are not shown in those placements, and context exclusions are not applied there.
***
## Next Steps
Learn how to edit, delete, and manage targeting sales.
***
## FAQ
Different targeting types apply with AND logic. For example, selecting "20s" and "Seoul" displays only to people in their 20s living in Seoul. Selecting multiple targeting options within the same targeting applies OR logic, so "20s, 30s" displays to people in their 20s or 30s.
Use Required Include when creating ad products for specific audiences. For example, to create a "20s-only product", set 20s as Required Include and do not expose other options of the same targeting for advertisers to select. If other options are exposed, advertisers can add them, and they are combined with 20s using OR, which widens the audience. Use Required Exclude for brand safety to prevent ads from displaying to certain targeting options.
# Campaign Booking Webhook
Source: https://docs.adrop.io/integrations/campaign-booking-webhook
Receive changes to campaigns booked on your project with a webhook, and look up booked campaigns with the API.
A booking happens when an advertiser's campaign is approved and takes your schedule and ad units. When you register a campaign booking webhook, Adrop sends an event to your URL each time a booked campaign is approved, changed, paused, resumed, canceled, started, or finished. You can also look up booked campaigns with the API.
| Part | Direction | Description |
| - | - | - |
| [Register a webhook](#register-a-webhook) | You → Adrop | Register, read, and delete the URL that receives events, with an API key. |
| [Receiving events](#receiving-events) | Adrop → You | Receive an event each time a booked campaign changes. |
| [Booked campaigns](#booked-campaigns) | You → Adrop | Look up booked campaigns. Use it for the first sync and to catch up after missed events. |
Events and lookups include the campaign ID and title, status, ad product ID and name, pricing type, budget, advertiser name and URL, schedule, the IDs and names of ad units, apps, targetings, and targeting options, targeting policies, and the Context IDs of options selected in context targeting. They don't include advertiser contact details such as email, creatives, rates or surcharges, payments, or review details.
***
## Prerequisites
* You need an API key with these two scopes. The project owner issues keys from **\[Settings]** > **\[Integrations]**. For how to issue a key, see [Integrations & API Keys](/integrations/overview).
| Scope | Used for |
| - | - |
| `campaign:subscribe` | Registering, reading, and deleting the webhook |
| `campaign:read` | Looking up booked campaigns |
* `campaign:subscribe` isn't selected by default when you create an API key, so select it yourself. You can't change the scopes of a key after it's issued.
* Your receiving server needs an `https` URL that's reachable from the internet.
Keep the API key on your server only, and never put it in apps or web pages. Events are signed with this key too, so if the key is exposed, revoke it right away and register the webhook again with a new key.
***
## Basic Information
| Item | Value |
| - | - |
| Base URL | `https://lake.adrop.io` |
| Authentication | API key in the `x-api-key` header. The key determines the project. |
| Content-Type | `application/json` |
| ID format | 26-character ULID, such as `01K6Y8X9Z0A1B2C3D4E5F6G7H8`. |
| Method | Path | Scope | Description |
| - | - | - | - |
| `PUT` | `/webhook` | `campaign:subscribe` | [Register or update the webhook](#register-a-webhook) |
| `GET` | `/webhook` | `campaign:subscribe` | [Get the webhook settings](#get-the-webhook-settings) |
| `DELETE` | `/webhook` | `campaign:subscribe` | [Delete the webhook](#delete-the-webhook) |
| `GET` | `/campaigns` | `campaign:read` | [List booked campaigns](#list-booked-campaigns) |
| `GET` | `/campaigns/{campaignId}` | `campaign:read` | [Get a booked campaign](#get-a-booked-campaign) |
***
## Register a Webhook
Registers the URL that receives events. A project can have one webhook. If one is already registered, this request updates it.
### Request
```http theme={null}
PUT /webhook
```
### Request Body
| Field | Type | Required | Description |
| - | - | - | - |
| `url` | string | Yes | The URL that receives events. It must start with `https`, up to 2,048 characters. |
| `events` | string\[] | No | The [event types](#event-types) to receive. Omit it or send an empty array to receive all events. |
* The existing settings are **replaced as a whole**. Send all the settings you want, even to change one of them. For example, omitting `events` switches back to receiving all events.
* Any other field fails with `400`.
* You can't register `localhost`, private or internal addresses, domains that resolve to a private IP, domains that can't be resolved, or URLs with user information (`https://user:pw@host`). These fail with `400 WEBHOOK_CONFIG_URL_NOT_ALLOWED`.
* If you register again with another API key, later events are signed with that key. See [Key Rotation and Revocation](#key-rotation-and-revocation).
* Campaigns booked before you register don't get events for that. After you register, use [Booked Campaigns](#booked-campaigns) once to sync the current state.
### Example Request
```bash theme={null}
curl -X PUT https://lake.adrop.io/webhook \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://partner.example.com/adrop/booking",
"events": ["booking.confirmed", "booking.updated", "booking.cancelled"]
}'
```
### Example Response
Returns `200` with the saved settings.
```json theme={null}
{
"_id": "01J8K2M3N4P5Q6R7S8T9V0W1X2",
"keyId": "01JRX7AAAAAAAAAAAAAAAAAAAA",
"url": "https://partner.example.com/adrop/booking",
"events": ["booking.confirmed", "booking.updated", "booking.cancelled"],
"active": true,
"createdAt": "2026-10-02T00:00:00.000Z",
"updatedAt": "2026-10-02T00:00:00.000Z"
}
```
| Field | Description |
| - | - |
| `_id` | Webhook settings ID |
| `keyId` | ID of the API key that signs events. Same as the `x-adrop-key-id` header of event requests. |
| `url` | The registered URL |
| `events` | The event types to receive. An empty array means all events. |
| `active` | Whether events are sent. It becomes `false` when the API key used for registration is revoked. |
| `createdAt`, `updatedAt` | Created and updated time (ISO 8601) |
## Get the Webhook Settings
```bash theme={null}
curl https://lake.adrop.io/webhook \
-H "x-api-key: YOUR_API_KEY"
```
* The response has the same format as the response of [Register a Webhook](#register-a-webhook).
* If nothing is registered, it returns `404 WEBHOOK_NOT_FOUND`.
* If `active` is `false`, events have stopped because the API key used for registration was revoked. Register again with a new key to resume them.
## Delete the Webhook
```bash theme={null}
curl -X DELETE https://lake.adrop.io/webhook \
-H "x-api-key: YOUR_API_KEY"
```
| Response | Meaning |
| - | - |
| `{ "result": true }` | The settings were deleted. No more events are sent. |
| `{ "result": false }` | There were no settings to delete. |
***
## Receiving Events
When a booked campaign changes, Adrop sends an event to your URL with `POST`.
### Event Types
| `eventType` | When it's sent |
| - | - |
| `booking.confirmed` | When a campaign is approved and its booking is confirmed. If the approval is withdrawn (`booking.cancelled`) and the campaign is approved again, it's sent again. |
| `booking.updated` | When the title, ad product, pricing type, budget, advertiser name or URL, schedule, targeting, or ad units of a booked campaign change |
| `booking.paused` | When a booked campaign is paused |
| `booking.resumed` | When a paused campaign is resumed |
| `booking.cancelled` | When a booking is released. This includes canceled or deleted campaigns and approved campaigns that the advertiser submits for review again, which withdraws the approval. It isn't sent for campaigns that were never approved. |
| `booking.started` | After the campaign's start time. It's also sent for paused campaigns. |
| `booking.finished` | After the campaign's end time |
* One change sends at most one event. Other values that changed at the same time are in that event's `payload`.
* `booking.updated` is sent only when a value that you can see actually changes. It's sent when a targeting option is deleted and the campaign's targeting conditions change.
* No event is sent in these cases. The new values appear in the next event and in [Booked Campaigns](#booked-campaigns).
* The **name** of an ad product, ad unit, app, targeting, or targeting option changes.
* A creative is approved, rejected, or changed.
* You create a new targeting option or move a sub-option out of its option group, and booked campaigns automatically get the option's settings.
* `booking.started` and `booking.finished` can arrive up to an hour after the time passes. Use `payload.startTime` and `payload.endTime` for the exact times. Each of the two events is sent only once, within 24 hours after the time passes.
### Request Headers
| Header | Description |
| - | - |
| `Content-Type` | `application/json` |
| `x-adrop-key-id` | ID of the API key that signed the request. Same as `keyId` in the webhook settings. |
| `x-adrop-signature` | Signature in the format `t={timestamp},v1={signature}`. See [Signature Verification](#signature-verification). |
| `x-adrop-event-id` | Same value as `eventId` in the body |
### Event Body
| Field | Type | Description |
| - | - | - |
| `eventId` | string | Event ID (ULID). It stays the same on resends, so use it as the key to prevent duplicate processing. |
| `eventType` | string | [Event type](#event-types) |
| `timestamp` | number | When the event happened, in epoch milliseconds. It's the time the campaign changed, and for `booking.started` and `booking.finished`, the time sending was decided. It isn't the send time, so an earlier event has a smaller value, and resends don't change it. |
| `payload` | object | Full information of the campaign at the time of the event. See [Campaign Information](#campaign-information). |
```http Example request theme={null}
POST /adrop/booking HTTP/1.1
Host: partner.example.com
Content-Type: application/json
x-adrop-key-id: 01JRX7AAAAAAAAAAAAAAAAAAAA
x-adrop-event-id: 01K6Z3A4B5C6D7E8F9G0H1J2K3
x-adrop-signature: t=1790899200000,v1=a3b8f2c4d5e6f7081a2b3c4d5e6f7081a2b3c4d5e6f7081a2b3c4d5e6f7081a2b
{
"eventId": "01K6Z3A4B5C6D7E8F9G0H1J2K3",
"eventType": "booking.confirmed",
"timestamp": 1790899200000,
"payload": {
"campaignId": "01K6Y8X9Z0A1B2C3D4E5F6G7H8",
"title": "Fall New Arrivals Promotion",
"status": "예약됨",
"product": "01HD38N9K3EA5HB322NJMTU1NN",
"productTitle": "Main Banner CPM",
"pricingType": "CPM",
"budget": 5000000,
"advertiser": { "name": "Adrop Demo Brand", "url": "https://brand.example.com" },
"startTime": 1791126000000,
"endTime": 1792421999000,
"units": [
{
"app": "01HD38P0M4FB6JC433PKFNV2PP",
"unit": "01HD38Q1R5GC7KD544QLGW3QQ",
"title": "Main Banner",
"appTitle": "Adrop Demo App"
}
],
"targetings": [
{
"targeting": "01HX5K8ZJ0TARGETING000001",
"title": "Interests",
"kind": "오디언스",
"policies": {
"01HX5K8ZJ0AAAAAAAAAAAAAAA1": { "policy": "any", "select": "included", "title": "Sports" }
}
},
{
"targeting": "01HX5K8ZJ0TARGETING000002",
"title": "Article Sections",
"kind": "문맥",
"policies": {
"01HX5K8ZJ0DDDDDDDDDDDDDDD4": {
"policy": "any",
"select": "included",
"title": "Travel",
"contexts": ["travel", "hotel", "flight"],
"contextCount": 3
}
}
}
]
}
}
```
Every event has the same structure. Only `eventType` and the values at that time differ. The order of keys in an object has no meaning.
### Campaign Information
`payload` and the results of [Booked Campaigns](#booked-campaigns) have the same structure. A `?` at the end of a field name marks an optional field whose key is omitted when it has no value.
| Field | Type | Description |
| - | - | - |
| `campaignId` | string | Campaign ID |
| `title?` | string | Campaign title entered by the advertiser |
| `status` | string | Campaign status at the time of the event or lookup. See [Status Values](#status-values). |
| `product` | string | ID of the booked ad product |
| `productTitle?` | string | Ad product name |
| `pricingType` | string | Pricing type at the time of booking: `CPM`, `CPC`, `CPP`, or `인하우스` (in-house). It's an empty string for an irregular campaign with no stored value. |
| `budget` | number | Campaign budget, in the ad product's currency. It isn't a settlement amount. |
| `advertiser?` | object | Advertiser information entered by the advertiser, with `name?` (advertiser name) and `url?` (advertiser URL) |
| `startTime` | number | Start time, in epoch milliseconds |
| `endTime` | number | End time, in epoch milliseconds |
| `units` | object\[] | Booked ad units. Each ad unit appears once. |
| `targetings` | object\[] | Targetings applied to the campaign. An empty array if there are none. |
Each item of `units` has these fields:
| Field | Description |
| - | - |
| `app` | App ID |
| `unit` | Ad unit ID |
| `title?` | Ad unit name |
| `appTitle?` | App name |
Each item of `targetings` has these fields. Match targetings and targeting options by ID, and use names only for people to read.
| Field | Description |
| - | - |
| `targeting` | Targeting ID |
| `title?` | Targeting name |
| `kind?` | Targeting type: `오디언스` (audience) or `문맥` (context) |
| `policies` | An object keyed by targeting option ID. Each value has the fields below. |
| `policies.*.policy` | The rule that the ad product sets for the option: `any` (Free Selection), `included` (Required Include), `excluded` (Required Exclude), or `none` (not used) |
| `policies.*.select?` | What the advertiser chose in the campaign: `included` (include) or `excluded` (exclude). If the advertiser didn't choose, the key is omitted or the value is `none`. |
| `policies.*.title?` | Targeting option name |
| `policies.*.contexts?` | Context IDs of an option that the campaign includes or excludes in context targeting, up to 200. For an option group, it's the list of its own Context IDs and those of its sub-options, without duplicates. |
| `policies.*.contextCount?` | Total number of `contexts`. If it's larger than the number of items in `contexts`, there are more than 200 and only some are included. |
* `contexts` and `contextCount` appear only for options that the campaign includes or excludes in context targeting. Audience targetings and options that aren't selected don't have them.
* If a selected option has no Context IDs, `contexts` is `[]` and `contextCount` is `0`.
* To get more than 200 Context IDs, use the [Context Targeting API](/integrations/context-targeting/overview).
Optional fields (`title`, `productTitle`, `advertiser`, `title` and `appTitle` in `units`, `title` and `kind` in `targetings`, and `title`, `contexts`, and `contextCount` in `policies`) are looked up when the event is sent. If the item was deleted and can't be found, the key is omitted and only the ID remains, so don't treat a missing optional field as an error.
### Status Values
`status` is one of the strings below. Compare the strings as they are.
| Value | Meaning |
| - | - |
| `예약됨` | Approved, before the start time. |
| `진행중` | After the start time, before the end. |
| `정지됨` | An approved campaign is paused before its end. |
| `종료됨` | The end time of an approved campaign has passed. A paused campaign is also `종료됨` after its end time. |
| `취소됨` | Canceled or deleted, or the end time passed while the approval was withdrawn. A canceled or deleted campaign is `취소됨` even after its end time. |
| `심사중` | Only in `booking.cancelled`. The advertiser submitted an approved campaign for review again before its end, which withdrew the approval. |
`status` doesn't depend on whether creatives are approved.
***
## Signature Verification
Every event request has an `x-adrop-signature` header. Verify the signature before you process the request.
```text theme={null}
x-adrop-signature: t={timestamp},v1={hex(HMAC-SHA256(signingSecret, "{timestamp}.{rawBody}"))}
```
* `signingSecret` is the SHA-256 hash, as a hexadecimal string, of the API key used to register the webhook. You compute it from the API key; it isn't issued separately.
* `t` is the signing time in epoch milliseconds. Reject requests where it differs from the current time by more than 5 minutes.
* Each resend has a new `t` and signature, but the body, including `eventId` and `timestamp`, stays the same.
* `x-adrop-key-id` tells you which API key signed the request. While you rotate keys, events signed with either key can arrive.
Verify the signature against the raw bytes of the request body you received. If you turn a parsed JSON object back into a string, the key order or whitespace can change and verification fails.
```javascript Node.js (Express) example theme={null}
const crypto = require('crypto')
const express = require('express')
const app = express()
// Keep the raw body for signature verification
app.use(express.json({
verify: (req, _res, buf) => { req.rawBody = buf }
}))
const sha256Hex = (value) => crypto.createHash('sha256').update(value).digest('hex')
// keyId → signingSecret. While you rotate keys, register the previous key too
const SIGNING_SECRETS = new Map([
[process.env.ADROP_KEY_ID, sha256Hex(process.env.ADROP_API_KEY)],
])
function parseSignature(header) {
if (!header) return null
const parts = Object.fromEntries(
header.split(',').map((part) => {
const i = part.indexOf('=')
return i > 0 ? [part.slice(0, i), part.slice(i + 1)] : [part, '']
})
)
const ts = Number(parts.t)
if (!Number.isFinite(ts) || !parts.v1) return null
return { ts, sig: parts.v1 }
}
function verifySignature(rawBody, signatureHeader, signingSecret) {
const parsed = parseSignature(signatureHeader)
if (!parsed) return false
if (Math.abs(Date.now() - parsed.ts) > 5 * 60 * 1000) return false
const expected = crypto
.createHmac('sha256', signingSecret)
.update(`${parsed.ts}.${rawBody.toString('utf8')}`)
.digest()
const provided = Buffer.from(parsed.sig, 'hex')
return expected.length === provided.length && crypto.timingSafeEqual(expected, provided)
}
app.post('/adrop/booking', async (req, res) => {
const signingSecret = SIGNING_SECRETS.get(req.get('x-adrop-key-id'))
if (!signingSecret || !verifySignature(req.rawBody, req.get('x-adrop-signature'), signingSecret)) {
return res.status(401).send('Invalid signature')
}
const { eventId, eventType, timestamp, payload } = req.body
// Follow "Event Handling Rules" below to process the event
await handleBookingEvent({ eventId, eventType, timestamp, payload })
res.sendStatus(200)
})
```
***
## Responses and Retries
* After you process an event, respond with `2xx` **within 10 seconds**. The response body isn't used. Hand long processing off to run asynchronously, and respond first.
* For a temporary failure, respond with `5xx`. `3xx` and `4xx` responses aren't retried.
| Item | Value |
| - | - |
| Response timeout | 10 seconds |
| Success | A `2xx` response |
| Attempts | Up to 5 (the first attempt and 4 retries) |
| Retry interval | Grows to about 10, 20, 40, and 80 seconds |
| Retried on | `5xx` responses, network errors, timeouts, and temporary DNS errors |
| Not retried on | `3xx` responses (redirects aren't followed), `4xx` responses, URLs that resolve to a private IP, and domains that don't exist |
| When all attempts fail | The event isn't sent again. To catch up on missed events, sync with [Booked Campaigns](#booked-campaigns). |
If you return `401` for a signature error, or a `4xx` such as `404` or `429` for a moment during a deployment, that event doesn't come again. Respond with `5xx` for temporary failures.
### Event Handling Rules
1. **Filter duplicates by `eventId`.** The same event can arrive up to 5 times. If an `eventId` you already processed arrives, don't process it again, and respond with `2xx`. Record the `eventId` only after processing succeeds, so that an event you answered with `5xx` after a failed save is processed again when it's resent.
2. **Don't rely on arrival order.** Resends and simultaneous changes can make events arrive out of order, and the size of `eventId` doesn't tell the order either. Keep the last `timestamp` you applied for each campaign, and ignore only events with a **smaller** `timestamp`. Apply events with the same `timestamp`.
3. **Overwrite with `payload`.** `payload` is always the full campaign information, so overwriting by `campaignId` brings you to the latest state even if you miss events or they arrive out of order. If you overwrite with a `payload` that lacks optional fields, the names you saved before are removed too.
4. **`eventType` is the change, and `status` is the state.** They don't always pair up. For example, `booking.cancelled` for a campaign whose approval was withdrawn before its end has `status` `심사중`, and `booking.finished` for a campaign that ended while paused has `종료됨`.
5. **`budget` isn't a settlement amount.** For settlement and revenue, use the [Report API](/integrations/reports).
### Choosing Events
If you choose events with `events` and pick only `booking.confirmed` and `booking.cancelled`, you miss changes after approval (`booking.updated`) to the schedule, targeting, ad units, budget, and advertiser information. To reflect changes, also receive `booking.updated`, or leave `events` empty to receive all events.
## Key Rotation and Revocation
Events are signed with **the API key that last registered the webhook**. To change keys, follow these steps.
Issue a new API key with the `campaign:read` and `campaign:subscribe` scopes.
Deploy your receiving server so that it verifies signatures with both the new and the previous key. Tell them apart by `x-adrop-key-id`.
Call `PUT /webhook` with the new key. Events sent after that are signed with the new key.
Events that were already being sent or waiting for a retry when you registered can arrive signed with the previous key. Wait at least 10 minutes.
Revoke the previous key in the console, and remove its verification from your receiving server.
If you revoke the API key used to register the webhook, the webhook is deactivated (`active: false`) and events stop. When you change keys, always revoke the previous key **after** you register again with the new key.
### Security Recommendations
* Keep API keys only in a secure place such as a secrets manager, and don't share them in config files, repositories, or messengers.
* If a key may have been exposed, revoke it in the console right away. Revocation takes effect immediately, and the webhook registered with that key is deactivated too.
* Don't process requests that fail signature verification. Mask the `x-adrop-signature` value in access logs.
***
## Booked Campaigns
Look up booked campaigns. Use it to sync the current state after you first register the webhook, or to catch up on missed events. It needs the `campaign:read` scope.
The results are approved campaigns whose end time hasn't passed, either running or not started yet. Each item has the same [campaign information](#campaign-information) structure as the event `payload`.
### List Booked Campaigns
```http theme={null}
GET /campaigns
```
| Query parameter | Type | Required | Description |
| - | - | - | - |
| `unit` | string | No | Return only campaigns that include this ad unit. Up to 64 characters |
| `next` | string | No | The `next` value from the previous response, to get the next page |
| `limit` | number | No | Items per page. 1–120, default 20 |
* Results are sorted by end time (`endTime`), latest first, then by campaign ID in descending order.
* If the response has `next`, there's another page. Send the `next` value as is in the next request. The last page has no `next`.
```bash Example request theme={null}
curl "https://lake.adrop.io/campaigns?unit=01HD38Q1R5GC7KD544QLGW3QQ&limit=20" \
-H "x-api-key: YOUR_API_KEY"
```
```json Example response theme={null}
{
"items": [
{
"campaignId": "01K6Y8X9Z0A1B2C3D4E5F6G7H8",
"title": "Fall New Arrivals Promotion",
"status": "예약됨",
"product": "01HD38N9K3EA5HB322NJMTU1NN",
"productTitle": "Main Banner CPM",
"pricingType": "CPM",
"budget": 5000000,
"advertiser": { "name": "Adrop Demo Brand", "url": "https://brand.example.com" },
"startTime": 1791126000000,
"endTime": 1792421999000,
"units": [
{
"app": "01HD38P0M4FB6JC433PKFNV2PP",
"unit": "01HD38Q1R5GC7KD544QLGW3QQ",
"title": "Main Banner",
"appTitle": "Adrop Demo App"
}
],
"targetings": []
}
],
"next": "01K6Y8X9Z0A1B2C3D4E5F6G7H8"
}
```
This example reads all booked campaigns from start to finish and saves them.
```javascript Full sync example theme={null}
async function syncAllBookings() {
let next
do {
const query = new URLSearchParams({ limit: '100', ...(next && { next }) })
const res = await fetch(`https://lake.adrop.io/campaigns?${query}`, {
headers: { 'x-api-key': process.env.ADROP_API_KEY },
})
if (!res.ok) throw new Error(`Request failed: ${res.status}`)
const { items, next: nextCursor } = await res.json()
for (const item of items) await saveBooking(item.campaignId, item)
next = nextCursor
} while (next)
}
```
Campaigns that aren't in the results have been canceled or have ended. To know whether and when a campaign was canceled, use the `booking.cancelled` event, not the lookup API.
### Get a Booked Campaign
```http theme={null}
GET /campaigns/{campaignId}
```
```bash Example request theme={null}
curl https://lake.adrop.io/campaigns/01K6Y8X9Z0A1B2C3D4E5F6G7H8 \
-H "x-api-key: YOUR_API_KEY"
```
* The response has the same structure as one item of `items` in the list.
* If `campaignId` isn't a ULID, it returns `400`.
* Canceled or finished campaigns, campaigns that aren't approved, campaigns of other projects, and campaigns that don't exist all return `404 CAMPAIGN_NOT_FOUND`. To know whether a campaign was canceled, use the `booking.cancelled` event.
***
## Errors
| HTTP | Code | Cause | What to do |
| - | - | - | - |
| `400` | (validation error) | `url` isn't `https` or is longer than 2,048 characters, an unknown event type in `events`, an undefined field, `limit` out of range, or a `campaignId` that isn't a ULID | Fix the request. |
| `400` | `WEBHOOK_CONFIG_URL_NOT_ALLOWED` | A URL that can't be registered: a private or internal address, a URL with user information, or a domain that resolves to a private IP or can't be resolved | Use a public `https` URL that's reachable from the internet. |
| `400` | `WEBHOOK_CONFIG_EVENT_TYPE_MISMATCH` | `events` has an event type that the campaign booking webhook doesn't send | Limit `events` to the 7 values in [Event Types](#event-types). |
| `401` | - | The API key is missing, invalid, or revoked | Check the API key. |
| `403` | - | The key doesn't have the required scope (`campaign:subscribe` for `/webhook`, `campaign:read` for `/campaigns`) | Use a key with the scope. |
| `404` | `WEBHOOK_NOT_FOUND` | `GET /webhook`: nothing is registered | Register with `PUT /webhook`. |
| `404` | `AUTH_KEY_NOT_FOUND` | The API key of the request was revoked or doesn't belong to this project | Send the request again with a valid key. |
| `404` | `CAMPAIGN_NOT_FOUND` | `GET /campaigns/{campaignId}`: a campaign that can't be looked up | Check the state with events or the list. |
| `502` | - | A temporary server error | Wait a moment, then send the same request again. Requests of this API are safe to repeat. |
The error code is in the `errorCode` or `message` field of the response body.
***
## Integration Checklist
* [ ] You issued an API key with the `campaign:read` and `campaign:subscribe` scopes and keep it secure.
* [ ] You registered an `https` URL with `PUT /webhook` and confirmed `active: true` with `GET /webhook`.
* [ ] You tested signature verification with valid, forged, and expired requests, and you verify against the raw body.
* [ ] You filter duplicates by `eventId`, and ignore only events with a smaller `timestamp` than the one you keep.
* [ ] You respond with `2xx` within 10 seconds, and with `5xx` for temporary failures.
* [ ] You don't treat missing optional fields as errors, and you treat a `contextCount` larger than the number of `contexts` as a partial list.
* [ ] You synced the currently booked campaigns once with `GET /campaigns`.
* [ ] You have a procedure to verify with both keys during key rotation.
***
## Related Documents
Issue API keys and set their scopes
Manage targeting options and Context IDs from your server
# Errors and Limits
Source: https://docs.adrop.io/integrations/context-targeting/errors
Learn how the Context Targeting API reports errors, how to retry requests safely, and which limits apply.
This page covers the error responses of the [Context Targeting API](/integrations/context-targeting/overview), how bulk requests report results, how to retry safely, and the limits that apply to requests.
***
## Error Responses
A failed request returns an HTTP error status and a JSON body in one of the forms below. Handle errors by the HTTP status and `errorCode`, not by the `message` text.
| Status | Form | `errorCode` |
| - | - | :-: |
| `400` | [Validation error](#validation-errors) | No |
| `400`, `404`, `409` | [Business error](#business-errors) | Yes |
| `401`, `403` | [Authentication or permission error](#authentication-and-permission-errors) | No |
| `413` | [Request body too large](#request-body-too-large) | No |
| `500`, `502` | [Server error](#server-errors) | No |
### Validation Errors
The request was rejected before processing. A field is missing or invalid, a name or Context ID breaks the rules, the request has an unknown field (such as `_id` in a create request), or a per-request limit is exceeded. Nothing in the request is applied, including bulk requests.
`message` is an array that lists every problem found.
```json theme={null}
{
"message": [
"property _id should not exist",
"name must be 2-36 characters of Korean, English letters, digits, spaces or ~%&+- (no leading or trailing spaces)"
],
"error": "Bad Request",
"statusCode": 400
}
```
When an ID in the path isn't a valid ULID, or the body isn't valid JSON, `message` is a single string.
```json theme={null}
{
"message": "Invalid ULID",
"error": "Bad Request",
"statusCode": 400
}
```
### Business Errors
The request is well-formed, but the server can't process it. For example, the name is already in use, or the targeting doesn't exist.
| Field | Type | Description |
| - | - | - |
| `statusCode` | number | HTTP status code |
| `errorCode` | string | Error code. See [Errors by Code](#errors-by-code). |
| `detail` | object | Extra information for some error codes. See [Detail Fields](#detail-fields). |
| `timestamp` | string | Time the error occurred. Included in some responses only. |
This is the response when you create a targeting with a name that another context targeting already uses:
```json theme={null}
{
"statusCode": 409,
"errorCode": "TARGETING_NAME_DUPLICATE",
"detail": {
"existingId": "01JAB3XK5Q8V7M2N4P6R9T0W1Y"
}
}
```
A single-item endpoint returns the error of its item as the response. If the item fails because of an unexpected error, the endpoint returns `502` instead. Bulk endpoints report errors per item in `results`. See [Bulk Responses](#bulk-responses).
### Authentication and Permission Errors
`401` means the `x-api-key` header is missing, or the key is invalid or revoked. `403` means the key doesn't have the scope that the endpoint requires.
```json theme={null}
{
"message": "Unauthorized",
"statusCode": 401
}
```
```json theme={null}
{
"message": "Forbidden",
"statusCode": 403
}
```
### Request Body Too Large
`413` means the request body is larger than 8 MB. Nothing is applied.
```json theme={null}
{
"statusCode": 413,
"message": "request entity too large"
}
```
### Server Errors
`502` means the request couldn't be completed because of a temporary server problem, and `500` means an unexpected server error. In both cases, the request may or may not have been applied. The `message` text can vary. See [Retries and Duplicate Requests](#retries-and-duplicate-requests).
```json theme={null}
{
"message": "Upstream service error",
"error": "Bad Gateway",
"statusCode": 502
}
```
***
## Error Codes
### Errors by Status
These errors have no `errorCode`.
| Status | When it occurs | What to do |
| - | - | - |
| `400` | The request failed validation. Nothing is applied. | Fix the request based on `message`. See [Limits](#limits) and [Name Rules](#name-rules). |
| `401` | The `x-api-key` header is missing, or the key is invalid or revoked. | Check the API key. |
| `403` | The key doesn't have the required scope: `context-targeting:read` for `GET` requests, and `context-targeting:write` for `POST`, `PATCH`, and `DELETE` requests. | Use a key that has the scope. You can't add a scope to an existing key, so create a new key in **\[Settings]** > **\[Integrations]**. See [Integrations & API Keys](/integrations/overview). |
| `413` | The request body is larger than 8 MB. | Split the request. Send non-ASCII text, such as Korean, as raw UTF-8 instead of `\uXXXX` escapes, for example with `ensure_ascii=False` in Python's `json.dumps` or `JSON_UNESCAPED_UNICODE` in PHP's `json_encode`. |
| `500`, `502` | A server problem. The request may or may not have been applied. | Wait a moment, then send the same request again. See [Retries and Duplicate Requests](#retries-and-duplicate-requests). |
### Errors by Code
| Status | `errorCode` | When it occurs | What to do |
| - | - | - | - |
| `400` | `AD_PRODUCT_TARGETING_POLICY_LIMIT_EXCEEDED` | The change would give an ad product more than 500 options set as **Required Include** or **Required Exclude**, across all targetings. This can happen when a new top-level option takes over a product's required setting, or when a sub-option takes over the option group's settings because you delete the option group or detach the sub-option. In an option bulk add, every new top-level option of that targeting fails. | In the console, reduce the required options of the products in `detail.products`, then send the request again. |
| `400` | `TARGETING_CATEGORY_LIMIT_EXCEEDED` | The targeting would have more than 100,000 options, including sub-options. | Delete options you no longer use, or use another targeting. |
| `400` | `TARGETING_CATEGORY_LOCKED` | You tried to move a top-level option into an option group while an ad product sets it as **Required Include** or **Required Exclude**, or while a campaign that hasn't ended includes or excludes it. | Remove those settings first. To put new options in an option group, add them with `parent` or `children` from the start. See [Move or Detach an Option](/integrations/context-targeting/option-groups#move-or-detach-an-option). |
| `400` | `TARGETING_CATEGORY_NOT_SUPPORTED` | The request doesn't match the option's type or the option group rules. See the cases below this table. | Fix the request. See [Constraints](/integrations/context-targeting/option-groups#constraints). |
| `400` | `TARGETING_CATEGORY_TREE_ITEMS_LIMIT_EXCEEDED` | An option bulk delete includes more than 150 option groups and sub-options. The whole request is rejected, and nothing is deleted. | Split the IDs into smaller requests, and send them one after another. |
| `400` | `TARGETING_COLLECTION_CONTEXT_DUPLICATED` | A Context ID that you're adding to an option group's own Context IDs is already an own Context ID of another option group in the project. This includes option groups that the same request creates or changes. | Remove the conflicting Context IDs. `detail.conflicts` lists up to 20 of them. Only the own Context IDs of option groups must be unique. Regular options and sub-options can share Context IDs. |
| `400` | `TARGETING_CONTEXT_LIMIT_EXCEEDED` | The change would exceed a Context ID limit: 50,000 Context IDs or 8 MB per option, or 200,000 Context IDs per project. | Remove Context IDs you no longer use. See [Limits](#limits). |
| `400` | `TARGETING_LOCKED` | You tried to delete a targeting while an approved campaign that hasn't ended includes or excludes one of its options. Nothing is deleted. | Delete the targeting after those campaigns end. |
| `404` | `PROJECT_NOT_FOUND` | The project that the API key belongs to no longer exists. | Use a key issued in an existing project. |
| `404` | `TARGETING_CATEGORY_NOT_FOUND` | The option doesn't exist or isn't in a context targeting of your project, or `parent` isn't an option of the same targeting. Resending a delete that already succeeded also returns this code. | Check the ID. For a resent delete, treat the option as deleted. |
| `404` | `TARGETING_NOT_FOUND` | The targeting doesn't exist, belongs to another project, or isn't a context targeting. Resending a delete that already succeeded also returns this code. | Check the ID. For a resent delete, treat the targeting as deleted. |
| `409` | `CONTEXT_TARGETING_WRITE_IN_PROGRESS` | Another API write for the same project is in progress, and nothing in the request was applied. In `results`, this error means that the item wasn't applied. | Wait `detail.retryAfterMs` milliseconds, then send it again. See [Concurrent Writes](#concurrent-writes). |
| `409` | `TARGETING_CATEGORY_NAME_DUPLICATE` | The targeting already has an option with the name, including option groups and sub-options, or the name repeats within the request. | Use another name. If you resent a create, use the ID in `detail.existingIds`. |
| `409` | `TARGETING_NAME_DUPLICATE` | The project already has a targeting with the name, of any kind including audience targetings, or the name repeats within the request. | Use another name. If you resent a create, use the ID in `detail.existingId`. |
| `500` | `UNCATCHED` | Bulk `results` only. An unexpected error occurred. In a create or delete bulk request, nothing in the request was applied. | Send the same request again. For an update bulk request, send the failed items again. |
`TARGETING_CATEGORY_NOT_SUPPORTED` occurs in these cases:
* An option group has neither sub-options nor Context IDs of its own.
* `children` is sent with `type: "atomic"`, or `exposeChildren` is sent for a regular option.
* When you add an option, `parent` is sent together with `type: "collection"`, `children`, or `exposeChildren`.
* `parent` points to an option that isn't a top-level option group.
* An update sends `parent` for an option group. Option groups can't be moved.
* A move, detach, or delete would remove the last sub-option of an option group that has no Context IDs of its own.
* An update would remove every Context ID of an option group that has no sub-options.
* The `parent` query parameter of [List Options](/integrations/context-targeting/options#list-options) points to a regular option.
* The option is stored in a form that the API can't change, such as an option group inside another option group.
### Detail Fields
| Field | Returned with | Description |
| - | - | - |
| `existingId` | `TARGETING_NAME_DUPLICATE` | ID of the existing targeting with the name. Included only when it's a context targeting. |
| `existingIds` | `TARGETING_CATEGORY_NAME_DUPLICATE` | IDs of the existing options with the name in the targeting. Usually one ID, but more if the targeting already has several options with that name. |
| `duplicateOfIndex` | `TARGETING_NAME_DUPLICATE`, `TARGETING_CATEGORY_NAME_DUPLICATE` | The name repeats within a create or option update request. This is the `index` of the item that used the name first, or the item's own `index` when the name repeats within one item. In a targeting rename request, a repeated name returns `existingId` instead, because the earlier rename is already applied. |
| `retryAfterMs` | `CONTEXT_TARGETING_WRITE_IN_PROGRESS` | How long to wait before sending the request again, in milliseconds. Between 0 and 2,000. |
| `targeting` | `TARGETING_CATEGORY_LIMIT_EXCEEDED` | ID of the targeting that would exceed the limit |
| `limit` | `TARGETING_CATEGORY_LIMIT_EXCEEDED`, `AD_PRODUCT_TARGETING_POLICY_LIMIT_EXCEEDED`, `TARGETING_CATEGORY_TREE_ITEMS_LIMIT_EXCEEDED` | The limit that would be exceeded |
| `count` | `AD_PRODUCT_TARGETING_POLICY_LIMIT_EXCEEDED`, `TARGETING_CATEGORY_TREE_ITEMS_LIMIT_EXCEEDED` | For the ad product limit, the number of required options that the change would leave on a product. If several products exceed the limit, the largest number. For the bulk delete limit, the number of option groups and sub-options in the request. |
| `products` | `AD_PRODUCT_TARGETING_POLICY_LIMIT_EXCEEDED` | IDs of the ad products that would exceed the limit |
| `conflicts` | `TARGETING_COLLECTION_CONTEXT_DUPLICATED` | Up to 20 conflicts. Each has `value` (the Context ID), `collection` (`_id` and `name` of the other option group), and `targeting` (`_id` and `name` of its targeting). If that option group is created or changed by the same request, `collection._id`, `targeting`, or `targeting.name` can be missing. |
***
## Bulk Responses
Bulk endpoints, such as `POST /context-targeting/bulk/create` and `POST /context-targeting/category/bulk/update`, return a result for each item.
### Response Fields
| Field | Type | Description |
| - | - | - |
| `results` | object\[] | Item results, in request order |
| `results[].index` | number | Position of the item in the request's `items` or `ids` array, starting at 0 |
| `results[].success` | boolean | `true` if the item succeeded |
| `results[].data` | object | Result of a successful item. The same object that the single-item endpoint returns. For deletes, only `_id`. |
| `results[].error` | object | Error of a failed item, with `statusCode`, `errorCode`, and, for some codes, `detail`. See [Errors by Code](#errors-by-code). |
| `succeeded` | number | Number of successful items |
| `failed` | number | Number of failed items |
### Response Status
When a bulk request is processed, it returns `200`, even if some or all items fail. Check `failed`, or `success` of each item.
In these cases, the whole request fails without `results`, and nothing in it is applied:
* `400` validation error. For example, the request has too many items, more than 10,000 Context IDs in total, or the same `_id` twice in an option bulk update.
* `400 TARGETING_CATEGORY_TREE_ITEMS_LIMIT_EXCEEDED` in an option bulk delete
* `401`, `403`, or `413`
* `404 PROJECT_NOT_FOUND`
* `409 CONTEXT_TARGETING_WRITE_IN_PROGRESS`
A `500` or `502` response also comes without `results`, but the request may or may not have been applied.
### How Items Are Applied
| Bulk request | How items are applied |
| - | - |
| Create targetings, delete targetings, add options, delete options | Together, as one unit. Items that fail a check before they're written, such as a duplicate name or an exceeded limit, fail on their own, and the other items are applied. If an unexpected error occurs, nothing in the request is applied: items that had already failed keep their errors, and every other item fails with `500 UNCATCHED`. Send the same request again. |
| Rename targetings, update options | Item by item. A failed item doesn't affect the others, and successful items stay applied. |
* An option bulk update applies items concurrently, but `results` stay in request order. Put all changes to one option in a single item. The same `_id` twice rejects the whole request.
* A later item in a rename or update request can use a name that an earlier item frees by renaming. If the earlier item fails, the later item fails with a name duplicate error.
### Example
The first item adds a Context ID to **Sports**. The second item tries to rename **Tech** to **Sports**, which is already in use.
```bash theme={null}
curl -X POST https://lake.adrop.io/context-targeting/category/bulk/update \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"items": [
{ "_id": "01JAB3XK5RA1B2C3D4E5F6G7H8", "contexts": { "add": ["basketball"] } },
{ "_id": "01JAB3XK5RA1B2C3D4E5F6G7H9", "name": "Sports" }
]
}'
```
The response status is `200`.
```json theme={null}
{
"results": [
{
"index": 0,
"success": true,
"data": {
"_id": "01JAB3XK5RA1B2C3D4E5F6G7H8",
"name": "Sports",
"type": "atomic",
"contextCount": 4,
"createdAt": "2026-10-13T01:23:45.000Z",
"updatedAt": "2026-10-13T02:10:00.000Z"
}
},
{
"index": 1,
"success": false,
"error": {
"statusCode": 409,
"errorCode": "TARGETING_CATEGORY_NAME_DUPLICATE",
"detail": {
"existingIds": ["01JAB3XK5RA1B2C3D4E5F6G7H8"]
}
}
}
],
"succeeded": 1,
"failed": 1
}
```
***
## Concurrent Writes
API writes for a project are processed one at a time. This covers every `POST`, `PATCH`, and `DELETE` request of the Context Targeting API, single-item and bulk, from any API key of the project. `GET` requests aren't affected, and writes for different projects don't wait for each other.
When a write arrives while another write for the project is in progress, the server waits briefly for it to finish. If it's still running, the request fails with `409 CONTEXT_TARGETING_WRITE_IN_PROGRESS`, and nothing in the request is applied. A bulk request fails as a whole, without `results`.
```json theme={null}
{
"statusCode": 409,
"errorCode": "CONTEXT_TARGETING_WRITE_IN_PROGRESS",
"timestamp": "2026-10-13T10:23:45+09:00",
"detail": {
"retryAfterMs": 2000
}
}
```
To handle this error:
1. Wait `detail.retryAfterMs` milliseconds. The value is between 0 and 2,000.
2. Send the same request again. If you get `409` again, repeat.
Send writes for a project in order: wait for the response to one request before you send the next. If several processes or workers write to the same project, each one should retry on `409`.
In rare cases, a request that runs unusually long is taken over by another request. Items that the first request hadn't applied fail with the same `409` in `results`, or as the response of a single-item endpoint. Items with this error weren't applied, so send only those items again.
***
## Retries and Duplicate Requests
If a request times out or returns `502` or another `5xx` status, you may not know whether it was applied. The server may still be processing the original request. Wait a moment, then send the same request again. If the original request is still in progress, the resend fails with `409 CONTEXT_TARGETING_WRITE_IN_PROGRESS`. Wait and send it again.
What happens when you resend a request that had already succeeded depends on the operation.
| Operation | Result of the resend |
| - | - |
| Create a targeting or add an option, single or bulk | Not idempotent. Each item that the original request created fails with a name duplicate error, and `detail` has the existing ID. |
| Rename a targeting or option | Same result. Sending the current name isn't an error. |
| Change Context IDs with `add`, `remove`, or `set` | Same result. `add` skips Context IDs that already exist, and `remove` ignores Context IDs that don't exist. |
| Move or detach an option with `parent` | Same result. An option already in the requested position stays there. |
| Change `exposeChildren` | Same result |
| Delete a targeting or option | `404 TARGETING_NOT_FOUND` or `TARGETING_CATEGORY_NOT_FOUND`. Treat it as deleted. |
### Resending a Create
The server always generates IDs, and `_id` in a create request fails with `400`. If you resend a create that had already succeeded, use the IDs in the `409` response instead of creating the item again.
* **Targeting**: `409 TARGETING_NAME_DUPLICATE`. `detail.existingId` is the ID of the targeting. To get the IDs of its options, see [Get a Targeting](/integrations/context-targeting/targetings#get-a-targeting).
* **Option**: `409 TARGETING_CATEGORY_NAME_DUPLICATE`. `detail.existingIds` has the IDs of the options with that name.
* In a bulk create, only the items that the original request created fail this way. The other items are processed as usual.
`detail.existingId` is included only when the existing targeting is a context targeting. If an audience targeting uses the name, choose another name. A name that repeats within the same request returns `detail.duplicateOfIndex` instead. Fix the request in that case.
Because API writes for a project are processed one at a time, two identical create requests sent at the same time don't both create. The later one fails with a name duplicate error or with `409 CONTEXT_TARGETING_WRITE_IN_PROGRESS`.
### After a 500 or 502
* `500 UNCATCHED` in the `results` of a create or delete bulk request: nothing in the request was applied. Send the same request again.
* `500 UNCATCHED` in the `results` of an update bulk request: that item failed. Send it again.
* A `500` or `502` response: the request may or may not have been applied. Send the same request again, and handle the result as shown in the table above.
If an option update has both `add` and `remove` and fails, the `add` may already be applied. Sending the same update again is safe.
***
## Limits
| Limit | Value | If exceeded |
| - | - | - |
| Items per targeting bulk request (create, rename, delete) | 1–100 | `400` validation error |
| Options per targeting create request, including sub-options, all items combined | 100 | `400` validation error |
| Items per option bulk request (add, update, delete) | 1–5,000 | `400` validation error |
| Options per option add request, including sub-options, all items combined | 5,000 | `400` validation error |
| Option group items per option bulk request: items that create an option group or have `parent` (add), items with `parent`, `null` included (update), IDs of option groups and sub-options (delete) | 150 | `400` validation error for add and update, `400 TARGETING_CATEGORY_TREE_ITEMS_LIMIT_EXCEEDED` for delete |
| Context IDs per request, all items combined, including `add`, `remove`, and `set` | 10,000 | `400` validation error |
| Length of a Context ID, after leading and trailing whitespace is trimmed | 1–256 bytes in UTF-8 | `400` validation error |
| Request body size | 8 MB | `413` |
| Context IDs per option (for an option group, its own Context IDs) | 50,000 | `400 TARGETING_CONTEXT_LIMIT_EXCEEDED` |
| Total size of an option's Context IDs (for an option group, its own Context IDs) | 8 MB | `400 TARGETING_CONTEXT_LIMIT_EXCEEDED` |
| Context IDs per project, across all options of all context targetings | 200,000 | `400 TARGETING_CONTEXT_LIMIT_EXCEEDED` |
| Options per targeting, including sub-options | 100,000 | `400 TARGETING_CATEGORY_LIMIT_EXCEEDED` |
| Options set as **Required Include** or **Required Exclude** per ad product, across all targetings | 500 | `400 AD_PRODUCT_TARGETING_POLICY_LIMIT_EXCEEDED` |
| Length of a targeting or option name | 2–36 characters | `400` validation error |
| `limit` of [List Options](/integrations/context-targeting/options#list-options) | 1–1,000 (default 1,000) | `400` validation error |
| `limit` of [Get an Option](/integrations/context-targeting/options#get-an-option) | 1–10,000 (default 10,000) | `400` validation error |
| `offset` of Get an Option | 0–50,000 (default 0) | `400` validation error |
| Sub-option IDs in an option group's `children` | First 50. `childCount` has the total. | — |
| Conflicts in `detail.conflicts` | Up to 20 | — |
* Limits on stored data (per option, per project, per targeting, and per ad product) are checked only when a change increases the amount. Changes that reduce it, such as removing Context IDs, aren't blocked even if the data is already over a limit.
* To upload more than one request can hold, split it into requests within these limits, and send them one after another. For example, add tens of thousands of options with option bulk add requests of up to 5,000 items each.
* To replace the Context IDs of an option that has more than 10,000, see [Updating Context IDs](/integrations/context-targeting/options#updating-context-ids).
* Send non-ASCII text, such as Korean names and Context IDs, as raw UTF-8 to keep the request body small.
***
## Name Rules
Targeting and option names, including option groups and sub-options, follow these rules.
| Rule | Targeting | Option |
| - | - | - |
| Length | 2–36 characters | 2–36 characters |
| Allowed characters | Korean, English letters, digits, spaces, and `~` `%` `&` `+` `-` | Same as targeting |
| Leading or trailing spaces | Not allowed. The API rejects the name instead of trimming it. | Same as targeting |
| Must be unique within | The project, across all targetings including audience targetings | The targeting, including option groups and sub-options |
| Error for an invalid name | `400` validation error | `400` validation error |
| Error for a duplicate name | `409 TARGETING_NAME_DUPLICATE` | `409 TARGETING_CATEGORY_NAME_DUPLICATE` |
* Other characters aren't allowed, such as Japanese or Chinese characters, `_`, `.`, and brackets.
* Names that repeat within one request are also duplicates. See `duplicateOfIndex` in [Detail Fields](#detail-fields).
* Renaming to the current name isn't an error.
* Options in different targetings can have the same name.
***
## Using the API with the Console
* **Same data**: The console and the API manage the same context targetings. Targetings and options that you create with the API appear in the console, and the API returns those created in the console.
* **Context targetings only**: Requests for an audience targeting fail with `404 TARGETING_NOT_FOUND`, and requests for its options fail with `404 TARGETING_CATEGORY_NOT_FOUND`. Audience targeting names still count for name uniqueness, so creating a context targeting with the same name fails with `409 TARGETING_NAME_DUPLICATE` without `detail.existingId`.
* **No write ordering for console edits**: The one-at-a-time processing applies only to API writes. A console edit doesn't wait for an API write and doesn't cause `409 CONTEXT_TARGETING_WRITE_IN_PROGRESS`, and an API write doesn't wait for a console edit.
* **Simultaneous changes can conflict**: When the console and the API change the same targeting at the same time, a change saved later can overwrite an earlier one, and two targetings or options can end up with the same name. Don't edit a targeting in the console while your integration writes to it. If it may have been changed in the console, get it again before you write.
* **Duplicate names**: If two targetings or options end up with the same name, rename or delete one of them. Before you delete an option, check [Effects on Ad Products and Campaigns](/integrations/context-targeting/options#effects-on-ad-products-and-campaigns).
* **Ad product settings**: To change which options an ad product sets as **Required Include** or **Required Exclude**, use the console. See [Selling Targeting](/targeting/sell).
# Option Groups
Source: https://docs.adrop.io/integrations/context-targeting/option-groups
Group targeting options into option groups and manage their sub-options with the Context Targeting API.
An option group is a targeting option that groups other options, called sub-options. With the API, you can create option groups, add and move sub-options, and choose whether advertisers can select sub-options individually.
The requests on this page need an API key with the `context-targeting:write` scope. Reading option groups needs `context-targeting:read`. See [Prerequisites](/integrations/context-targeting/overview#prerequisites).
The examples build a **Sports** option group with the sub-options **Baseball** and **Soccer** in the **News Sections** targeting (`01JAB3XK5Q8V7M2N4P6R9T0W1Y`). They assume the targeting has no other option named **Sports**, because option names must be unique within a targeting.
***
## What Is an Option Group
An option group matches its own Context IDs and the Context IDs of its sub-options. With the structure below, a campaign that includes **Sports** matches ad requests whose Context ID is `sport`, `baseball`, or `soccer`.
| Option | Type | Own Context IDs | Matches |
| - | - | - | - |
| **Sports** | Option group | `sport` | `sport`, `baseball`, `soccer` |
| **Baseball** | Sub-option of Sports | `baseball` | `baseball` |
| **Soccer** | Sub-option of Sports | `soccer` | `soccer` |
* **One level only**: Sub-options are always regular options (`type: "atomic"`). An option group is always a top-level option and can't contain another option group.
* **Never empty**: An option group needs at least one sub-option or one Context ID of its own.
* **Unique own Context IDs**: An option group's own Context IDs can't also be own Context IDs of another option group in the project. See [Constraints](#constraints).
* **Names**: Option group and sub-option names follow the [name rules](/integrations/context-targeting/errors#name-rules) and must be unique within the targeting, including option groups and sub-options. A duplicate fails with `409 TARGETING_CATEGORY_NAME_DUPLICATE`.
In the [option object](/integrations/context-targeting/options#option-object), option groups and sub-options have these fields:
| Field | Type | Description |
| - | - | - |
| `type` | string | `collection` for an option group. Sub-options are `atomic`. |
| `contextCount` | number | For an option group, the number of distinct Context IDs it matches: its own and its sub-options' |
| `ownContextCount` | number | Option group only. Number of its own Context IDs |
| `exposeChildren` | boolean | Option group only. Whether sub-options are shown to advertisers. `false` if never set |
| `children` | string\[] | Option group only. IDs of the first 50 sub-options |
| `childCount` | number | Option group only. Total number of sub-options |
| `parent` | string | Sub-option only. ID of the option group |
To read an option group:
* [Get an Option](/integrations/context-targeting/options#get-an-option) returns only the option group's own Context IDs in `contexts`, paged with `offset` and `limit` (`ownContextCount` in total). Read each sub-option's Context IDs from the sub-option.
* When `childCount` is more than 50, list all sub-options with [List Options](/integrations/context-targeting/options#list-options) and the `parent` query parameter set to the option group ID.
To rename an option group or a sub-option, or to change its Context IDs, use [Update an Option](/integrations/context-targeting/options#update-an-option) as you would for any option. For an option group, `contexts` changes only its own Context IDs. Changes reach ad serving after a short delay. See [When Changes Take Effect](/integrations/context-targeting/overview#when-changes-take-effect).
***
## Create an Option Group
Use the same endpoint as [Add an Option](/integrations/context-targeting/options#add-an-option) and send `type: "collection"`, `children`, or both. Each item in `children` becomes a new sub-option, created together with the option group.
### Request
```http theme={null}
POST /context-targeting/{targetingId}/category
```
### Path Parameters
| Parameter | Type | Required | Description |
| - | - | :-: | - |
| `targetingId` | string | Yes | ID of the targeting to add the option group to |
### Request Body
| Field | Type | Required | Description |
| - | - | :-: | - |
| `name` | string | Yes | Option group name |
| `type` | string | No | `collection`. You can leave it out when you send `children` |
| `contexts` | string\[] | No | The option group's own Context IDs |
| `children` | object\[] | No | Sub-options to create with the option group |
| `children[].name` | string | Yes | Sub-option name |
| `children[].contexts` | string\[] | No | Context IDs of the sub-option |
| `exposeChildren` | boolean | No | Show sub-options to advertisers. Default `false`. See [Show Sub-options to Advertisers](#show-sub-options-to-advertisers) |
* Send at least one item in `children` or one Context ID in `contexts`. An option group with neither fails with `400 TARGETING_CATEGORY_NOT_SUPPORTED`. So does `type: "atomic"` with `children`.
* An item in `children` accepts only `name` and `contexts`. A sub-option can't have sub-options of its own.
* If one of the option group's own Context IDs is already an own Context ID of another option group in the project, the request fails with `400 TARGETING_COLLECTION_CONTEXT_DUPLICATED`. See [Constraints](#constraints).
* A new option group is a new top-level option, so [Effects on Ad Products and Campaigns](/integrations/context-targeting/options#effects-on-ad-products-and-campaigns) applies to it as to any new option.
You can also create option groups when you [create a targeting](/integrations/context-targeting/targetings#create-a-targeting), with the same fields in each `categories` item, or with [Add Options in Bulk](/integrations/context-targeting/options#add-options-in-bulk).
### Example Request
```bash theme={null}
curl -X POST https://lake.adrop.io/context-targeting/01JAB3XK5Q8V7M2N4P6R9T0W1Y/category \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Sports",
"type": "collection",
"contexts": ["sport"],
"children": [
{ "name": "Baseball", "contexts": ["baseball"] }
]
}'
```
To create an option group with only its own Context IDs, keep `type: "collection"` and leave out `children`:
```json theme={null}
{
"name": "Sports",
"type": "collection",
"contexts": ["sport"]
}
```
### Example Response
The API returns `201` with the new option group.
```json theme={null}
{
"_id": "01JAB3XK5SB1C2D3E4F5G6H7J8",
"name": "Sports",
"type": "collection",
"contextCount": 2,
"ownContextCount": 1,
"exposeChildren": false,
"children": ["01JAB3XK5TC1D2E3F4G5H6J7K8"],
"childCount": 1,
"createdAt": "2026-10-13T01:23:45.000Z",
"updatedAt": "2026-10-13T01:23:45.000Z"
}
```
***
## Add an Option to an Option Group
To add a new sub-option to an existing option group, send `parent` with the option group ID to the [Add an Option](/integrations/context-targeting/options#add-an-option) endpoint.
* `parent` must be a top-level option group in the same targeting. If the targeting has no option with that ID, the request fails with `404 TARGETING_CATEGORY_NOT_FOUND`. If the option isn't an option group, it fails with `400 TARGETING_CATEGORY_NOT_SUPPORTED`.
* The new option is a regular option. `type: "collection"`, `children`, or `exposeChildren` together with `parent` fails with `400 TARGETING_CATEGORY_NOT_SUPPORTED`.
* The option group starts matching the new option's Context IDs. Campaigns that include the option group now also target them, and campaigns that exclude it now also exclude them. Ad product settings don't change.
To put an existing option into an option group, see [Move or Detach an Option](#move-or-detach-an-option).
### Request
```http theme={null}
POST /context-targeting/{targetingId}/category
```
### Path Parameters
| Parameter | Type | Required | Description |
| - | - | :-: | - |
| `targetingId` | string | Yes | ID of the targeting that has the option group |
### Request Body
| Field | Type | Required | Description |
| - | - | :-: | - |
| `name` | string | Yes | Sub-option name |
| `contexts` | string\[] | No | Context IDs of the sub-option |
| `parent` | string | Yes | ID of the option group to add the option to |
### Example Request
```bash theme={null}
curl -X POST https://lake.adrop.io/context-targeting/01JAB3XK5Q8V7M2N4P6R9T0W1Y/category \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Soccer",
"contexts": ["soccer"],
"parent": "01JAB3XK5SB1C2D3E4F5G6H7J8"
}'
```
### Example Response
The API returns `201` with the new sub-option.
```json theme={null}
{
"_id": "01JAB3XK5TC1D2E3F4G5H6J7K9",
"name": "Soccer",
"type": "atomic",
"contextCount": 1,
"parent": "01JAB3XK5SB1C2D3E4F5G6H7J8",
"createdAt": "2026-10-13T01:25:10.000Z",
"updatedAt": "2026-10-13T01:25:10.000Z"
}
```
***
## Move or Detach an Option
Send `parent` to [Update an Option](/integrations/context-targeting/options#update-an-option) to change where an option sits.
* An option group ID moves the option under that option group, from the top level or from another option group.
* `null` detaches a sub-option to the top level.
| Move | Effect on ad products and campaigns |
| - | - |
| Top level → option group | The option's own settings in ad products are removed. The move fails with `400 TARGETING_CATEGORY_LOCKED` if an ad product sets the option as **Required Include** or **Required Exclude**, or if a campaign that hasn't ended includes or excludes it (deleted or canceled campaigns and drafts created by advertisers don't count). Remove those settings first. |
| Option group → top level | The option takes over the option group's settings in ad products and campaigns, so campaigns that included or excluded the option group still include or exclude the option's Context IDs. If this would give an ad product more than 500 options set as **Required Include** or **Required Exclude**, the request fails with `400 AD_PRODUCT_TARGETING_POLICY_LIMIT_EXCEEDED`. |
| Option group → another option group | Ad products and campaigns don't change. |
The option group that receives the option starts matching its Context IDs, and the option group that the option leaves stops matching them.
* You can't move an option group. `parent` on an option group fails with `400 TARGETING_CATEGORY_NOT_SUPPORTED`.
* The destination must be a top-level option group in the same targeting. Otherwise the request fails with `404 TARGETING_CATEGORY_NOT_FOUND` or `400 TARGETING_CATEGORY_NOT_SUPPORTED`.
* You can't move or detach the last sub-option of an option group that has no Context IDs of its own. The request fails with `400 TARGETING_CATEGORY_NOT_SUPPORTED`.
The lock applies even when the option got its **Required Include**, **Required Exclude**, or campaign include or exclude automatically, for example from its option group when it was detached or when the option group was deleted. Until that setting is removed, you can't move the option back under an option group with the API. To keep options in an option group, create them with `parent` or `children` from the start.
### Request
```http theme={null}
PATCH /context-targeting/category/{categoryId}
```
### Path Parameters
| Parameter | Type | Required | Description |
| - | - | :-: | - |
| `categoryId` | string | Yes | ID of the option to move |
### Request Body
| Field | Type | Required | Description |
| - | - | :-: | - |
| `parent` | string or null | Yes | Option group ID to move the option under, or `null` to detach it to the top level |
| `name` | string | No | New name, applied together with the move. If the move fails, the name doesn't change either |
`parent` can't be combined with `contexts`. Change Context IDs in a separate request.
### Example Request
Detach **Soccer** to the top level:
```bash theme={null}
curl -X PATCH https://lake.adrop.io/context-targeting/category/01JAB3XK5TC1D2E3F4G5H6J7K9 \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "parent": null }'
```
To move it back under **Sports**, send the option group ID instead:
```json theme={null}
{
"parent": "01JAB3XK5SB1C2D3E4F5G6H7J8"
}
```
### Example Response
The API returns `200` with the updated option. A detached option has no `parent`.
```json theme={null}
{
"_id": "01JAB3XK5TC1D2E3F4G5H6J7K9",
"name": "Soccer",
"type": "atomic",
"contextCount": 1,
"createdAt": "2026-10-13T01:25:10.000Z",
"updatedAt": "2026-10-13T01:30:00.000Z"
}
```
***
## Show Sub-Options to Advertisers
By default, advertisers can select an option group only as a whole. Set `exposeChildren` to `true` to let advertisers include or exclude each sub-option on its own when they book a campaign.
* Advertisers can select sub-options only when the ad product offers the option group as **Free Selection**. See [Selling Targeting](/targeting/sell).
* `exposeChildren` doesn't change what the option group matches. Changing it doesn't modify existing ad products or campaigns.
* Only option groups accept `exposeChildren`. Sending it for any other option fails with `400 TARGETING_CATEGORY_NOT_SUPPORTED`. Send `true` or `false`; `null` fails with `400`.
* You can also set it when you [create the option group](#create-an-option-group).
### Request
```http theme={null}
PATCH /context-targeting/category/{categoryId}
```
### Path Parameters
| Parameter | Type | Required | Description |
| - | - | :-: | - |
| `categoryId` | string | Yes | Option group ID |
### Request Body
| Field | Type | Required | Description |
| - | - | :-: | - |
| `exposeChildren` | boolean | Yes | `true` shows sub-options to advertisers, `false` hides them |
You can send `name` and `contexts` in the same request.
### Example Request
```bash theme={null}
curl -X PATCH https://lake.adrop.io/context-targeting/category/01JAB3XK5SB1C2D3E4F5G6H7J8 \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "exposeChildren": true }'
```
### Example Response
The API returns `200` with the updated option group.
```json theme={null}
{
"_id": "01JAB3XK5SB1C2D3E4F5G6H7J8",
"name": "Sports",
"type": "collection",
"contextCount": 3,
"ownContextCount": 1,
"exposeChildren": true,
"children": ["01JAB3XK5TC1D2E3F4G5H6J7K8", "01JAB3XK5TC1D2E3F4G5H6J7K9"],
"childCount": 2,
"createdAt": "2026-10-13T01:23:45.000Z",
"updatedAt": "2026-10-13T01:35:00.000Z"
}
```
***
## Delete an Option Group
Delete an option group with [Delete an Option](/integrations/context-targeting/options#delete-an-option). Its sub-options aren't deleted:
* Sub-options move to the top level and take over the option group's settings in ad products and campaigns. For example, if an ad product sets **Sports** as **Required Include**, **Baseball** and **Soccer** become **Required Include**, and a campaign that included Sports now includes Baseball and Soccer.
* The option group's own Context IDs are deleted and stop matching. In the example, campaigns that included Sports no longer match `sport`.
* If taking over the settings would give an ad product more than 500 options set as **Required Include** or **Required Exclude**, the request fails with `400 AD_PRODUCT_TARGETING_POLICY_LIMIT_EXCEEDED` and nothing is deleted.
Campaigns don't block the deletion. If the option group has no sub-options, campaigns that included it lose that condition. See [Effects on Ad Products and Campaigns](/integrations/context-targeting/options#effects-on-ad-products-and-campaigns).
In the console, **Delete** on a group also deletes its sub-options. Deleting an option group with the API works like **Ungroup** in the console: its sub-options stay as top-level options.
### Request
```http theme={null}
DELETE /context-targeting/category/{categoryId}
```
### Path Parameters
| Parameter | Type | Required | Description |
| - | - | :-: | - |
| `categoryId` | string | Yes | Option group ID |
### Example Request
```bash theme={null}
curl -X DELETE https://lake.adrop.io/context-targeting/category/01JAB3XK5SB1C2D3E4F5G6H7J8 \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json"
```
### Example Response
The API returns `200` with the ID of the deleted option group.
```json theme={null}
{
"_id": "01JAB3XK5SB1C2D3E4F5G6H7J8"
}
```
***
## Constraints
**Structure**
* An option group can't be moved, and an option's type can't change after it's created. To group existing options, create an option group and [move](#move-or-detach-an-option) the options under it. A new option group can't be empty, so create it with a Context ID of its own or a new sub-option.
* Sub-options created in `children` count toward the per-request and per-targeting option limits. See [Limits](/integrations/context-targeting/errors#limits).
**Own Context IDs are unique among option groups**
* An option group's own Context IDs can't be own Context IDs of another option group in the project, in any context targeting. Regular options and sub-options can share Context IDs with any option.
* The rule applies when you create an option group and when you add own Context IDs to an option group with `add` or `set`. A conflict fails with `400 TARGETING_COLLECTION_CONTEXT_DUPLICATED`, and `detail.conflicts` lists up to 20 of the conflicting Context IDs with the option groups that already have them. See [Error Codes](/integrations/context-targeting/errors#error-codes).
**An option group can't become empty**
| Request | Result |
| - | - |
| Delete, move, or detach the last sub-option of an option group that has no Context IDs of its own | `400 TARGETING_CATEGORY_NOT_SUPPORTED`. Delete the option group instead; the sub-option moves to the top level. |
| Remove the last own Context ID of an option group that has no sub-options, with `remove` or `set: []` | `400 TARGETING_CATEGORY_NOT_SUPPORTED` |
An option group with its own Context IDs can lose all of its sub-options. It stays as an option group with only its own Context IDs.
**Option group items per bulk request**
In one option bulk request, at most 150 items can be option group items. A request with more fails as a whole with `400`, and nothing is applied. Split the items and send the requests one after another.
| Bulk request | Option group items |
| - | - |
| [Add Options in Bulk](/integrations/context-targeting/options#add-options-in-bulk) | Items that create an option group (`type: "collection"` or `children`), and items with `parent` |
| [Update Options in Bulk](/integrations/context-targeting/options#update-options-in-bulk) | Items with `parent`, including `null` |
| [Delete Options in Bulk](/integrations/context-targeting/options#delete-options-in-bulk) | IDs of option groups and sub-options |
* For a bulk delete, the error code is `TARGETING_CATEGORY_TREE_ITEMS_LIMIT_EXCEEDED`. `detail.limit` is the limit, and `detail.count` is the number of option groups and sub-options in your request.
* Renaming an option group, changing its own Context IDs, and setting `exposeChildren` don't count as option group items.
**Don't delete an option group and its sub-options in the same bulk request**
The result depends on their order in `ids`:
* **Option group first**: The sub-options move to the top level, take over the option group's settings, and are then deleted as top-level options. Campaigns that included the option group can lose that condition.
* **Sub-options first**: If the option group has no Context IDs of its own, deleting the last sub-option fails with `400 TARGETING_CATEGORY_NOT_SUPPORTED`. That option then moves to the top level when the option group is deleted.
# Targeting Options
Source: https://docs.adrop.io/integrations/context-targeting/options
List, add, update, and delete targeting options and keep their Context IDs up to date with the Context Targeting API.
A targeting option (option) is a named set of Context IDs in a context targeting, such as "Sports" with `sport`, `baseball`, and `soccer`. In API paths and fields, an option is a `category`, and its Context IDs are `contexts`.
An option has one of two types:
* `atomic`: a list of Context IDs.
* `collection`: an option that matches its own Context IDs and those of its sub-options. See [Option groups](/integrations/context-targeting/option-groups#what-is-an-option-group).
Paths on this page are relative to `https://lake.adrop.io`. For authentication and request format, see [Basic Information](/integrations/context-targeting/overview#basic-information).
***
## Option Object
The endpoints on this page return options in this shape. The object holds the number of Context IDs, not the Context IDs themselves. To read them, use [Get an Option](#get-an-option).
| Field | Type | Description |
| - | - | - |
| `_id` | string | Option ID. A 26-character ULID issued by the server |
| `name` | string | Option name |
| `type` | string | `atomic` or `collection` |
| `contextCount` | number | Number of Context IDs the option matches. For an option group, the number of distinct Context IDs across its own Context IDs and those of its sub-options |
| `ownContextCount` | number | Option group only. Number of the option group's own Context IDs |
| `exposeChildren` | boolean | Option group only. Whether sub-options are shown to advertisers. `false` if it was never set |
| `children` | string\[] | Option group only. IDs of the first 50 sub-options, in the option group's order |
| `childCount` | number | Option group only. Total number of sub-options. If it's larger than the length of `children`, read the rest with the `parent` parameter of [List Options](#list-options) |
| `parent` | string | Sub-option only. ID of the option group the option belongs to |
| `createdAt` | string | Time the option was created (ISO 8601, UTC) |
| `updatedAt` | string | Time the option was last updated (ISO 8601, UTC) |
Fields that don't apply to an option are omitted.
```json Regular option theme={null}
{
"_id": "01JAB3XK5RA1B2C3D4E5F6G7H8",
"name": "Sports",
"type": "atomic",
"contextCount": 3,
"createdAt": "2026-10-13T01:23:45.000Z",
"updatedAt": "2026-10-13T01:23:45.000Z"
}
```
```json Option group theme={null}
{
"_id": "01JAB3XK5SB1C2D3E4F5G6H7J8",
"name": "Sports",
"type": "collection",
"contextCount": 3,
"ownContextCount": 1,
"exposeChildren": false,
"children": ["01JAB3XK5TC1D2E3F4G5H6J7K8", "01JAB3XK5TC1D2E3F4G5H6J7K9"],
"childCount": 2,
"createdAt": "2026-10-13T01:23:45.000Z",
"updatedAt": "2026-10-13T01:23:45.000Z"
}
```
```json Sub-option theme={null}
{
"_id": "01JAB3XK5TC1D2E3F4G5H6J7K8",
"name": "Baseball",
"type": "atomic",
"contextCount": 1,
"parent": "01JAB3XK5SB1C2D3E4F5G6H7J8",
"createdAt": "2026-10-13T01:23:45.000Z",
"updatedAt": "2026-10-13T01:23:45.000Z"
}
```
***
## List Options
Returns the options of a targeting page by page, in ascending `_id` order (roughly the order they were created). Option groups and sub-options are included. Each item is an [option object](#option-object).
### Request
```http theme={null}
GET /context-targeting/{targetingId}/categories
```
Requires the `context-targeting:read` scope.
### Path Parameters
| Parameter | Type | Required | Description |
| - | - | :-: | - |
| `targetingId` | string | Yes | Targeting ID |
### Query Parameters
| Parameter | Type | Required | Description |
| - | - | :-: | - |
| `next` | string | No | Cursor for the next page. Pass the `next` value of the previous response. Omit it for the first page |
| `limit` | number | No | Maximum number of options per page, from 1 to 1,000. Default 1,000 |
| `parent` | string | No | Option group ID. Returns only the sub-options of that option group |
### Example Request
```bash theme={null}
curl "https://lake.adrop.io/context-targeting/01JAB3XK5Q8V7M2N4P6R9T0W1Y/categories?limit=1" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json"
```
### Example Response
```json theme={null}
{
"items": [
{
"_id": "01JAB3XK5RA1B2C3D4E5F6G7H8",
"name": "Sports",
"type": "atomic",
"contextCount": 3,
"createdAt": "2026-10-13T01:23:45.000Z",
"updatedAt": "2026-10-13T01:23:45.000Z"
}
],
"next": "01JAB3XK5RA1B2C3D4E5F6G7H8"
}
```
To get the next page, send the same request with `next=01JAB3XK5RA1B2C3D4E5F6G7H8`.
* The last page has no `next`. If you follow `next` until it's absent, you get every option exactly once.
* A page can have fewer items than `limit` even when more pages follow. Stop only when `next` is absent.
* There's no search by name. Filter the items by `name`, `parent`, or `children` on your side.
**Errors**
| Status | `errorCode` | When |
| - | - | - |
| 404 | `TARGETING_NOT_FOUND` | The targeting doesn't exist, belongs to another project, or isn't a context targeting |
| 404 | `TARGETING_CATEGORY_NOT_FOUND` | `parent` isn't an option of this targeting |
| 400 | `TARGETING_CATEGORY_NOT_SUPPORTED` | `parent` isn't an option group |
***
## Get an Option
Returns an option with a page of its Context IDs.
### Request
```http theme={null}
GET /context-targeting/category/{categoryId}
```
Requires the `context-targeting:read` scope.
### Path Parameters
| Parameter | Type | Required | Description |
| - | - | :-: | - |
| `categoryId` | string | Yes | Option ID |
### Query Parameters
| Parameter | Type | Required | Description |
| - | - | :-: | - |
| `offset` | number | No | Position of the first Context ID to return, from 0 to 50,000. Default 0 |
| `limit` | number | No | Maximum number of Context IDs to return, from 1 to 10,000. Default 10,000 |
### Example Request
```bash theme={null}
curl "https://lake.adrop.io/context-targeting/category/01JAB3XK5RA1B2C3D4E5F6G7H8?offset=0&limit=10000" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json"
```
### Example Response
```json theme={null}
{
"_id": "01JAB3XK5RA1B2C3D4E5F6G7H8",
"name": "Sports",
"type": "atomic",
"contextCount": 3,
"createdAt": "2026-10-13T01:23:45.000Z",
"updatedAt": "2026-10-13T01:23:45.000Z",
"targeting": "01JAB3XK5Q8V7M2N4P6R9T0W1Y",
"contexts": ["sport", "baseball", "soccer"],
"offset": 0,
"limit": 10000
}
```
The response is the [option object](#option-object) with these fields added:
| Field | Type | Description |
| - | - | - |
| `targeting` | string | ID of the targeting the option belongs to |
| `contexts` | string\[] | Context IDs in this page |
| `offset` | number | `offset` of this page |
| `limit` | number | `limit` of this page |
* To read all Context IDs of an option, increase `offset` by `limit` until `offset` reaches `contextCount`.
* For an option group, `contexts` holds only the option group's own Context IDs, so page through them up to `ownContextCount`. To read the Context IDs of a sub-option, get the sub-option.
**Errors**
| Status | `errorCode` | When |
| - | - | - |
| 404 | `TARGETING_CATEGORY_NOT_FOUND` | The option doesn't exist, belongs to another project, or isn't in a context targeting |
***
## Add an Option
Adds an option to an existing targeting. You can add a regular option, an option group, or a sub-option of an existing option group.
### Request
```http theme={null}
POST /context-targeting/{targetingId}/category
```
Requires the `context-targeting:write` scope. The response is `201` with the new [option object](#option-object).
### Path Parameters
| Parameter | Type | Required | Description |
| - | - | :-: | - |
| `targetingId` | string | Yes | ID of the targeting to add the option to |
### Request Body
| Field | Type | Required | Description |
| - | - | :-: | - |
| `name` | string | Yes | Option name. It must be unique in the targeting, including option groups and sub-options. See [Name Rules](/integrations/context-targeting/errors#name-rules) |
| `contexts` | string\[] | No | Context IDs. For an option group, its own Context IDs. See [Context ID Rules](#context-id-rules) |
| `type` | string | No | `atomic` or `collection`. If omitted, the option is an option group when `children` is present (`[]` included) and a regular option otherwise |
| `children` | object\[] | No | Option group only. Sub-options to create with the option group. Each item has `name` (required) and `contexts`. Sub-options are always regular options |
| `exposeChildren` | boolean | No | Option group only. Shows sub-options to advertisers. Default `false`. See [Show Sub-options to Advertisers](/integrations/context-targeting/option-groups#show-sub-options-to-advertisers) |
| `parent` | string | No | ID of a top-level option group in the same targeting. Adds the new option as a sub-option of that option group |
The server issues all IDs. A body with `_id` or any other field not listed here fails with `400`.
These combinations fail with `400 TARGETING_CATEGORY_NOT_SUPPORTED`, and nothing is created:
* An option group with no sub-options and no Context IDs of its own
* `type: "atomic"` with `children`
* `exposeChildren` on a regular option
* `parent` with `type: "collection"`, `children`, or `exposeChildren`. An option added to an option group is always a regular option
* `parent` set to an option that isn't a top-level option group
One request can create up to 5,000 options, counting an option group and its sub-options, and can carry up to 10,000 Context IDs in total. To add many options in one request, use [Add Options in Bulk](#add-options-in-bulk).
### Example Request
```bash theme={null}
curl -X POST https://lake.adrop.io/context-targeting/01JAB3XK5Q8V7M2N4P6R9T0W1Y/category \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Tech",
"contexts": ["tech", "ai", "mobile"]
}'
```
### Example Response
```json theme={null}
{
"_id": "01JAB3XK5RA1B2C3D4E5F6G7H9",
"name": "Tech",
"type": "atomic",
"contextCount": 3,
"createdAt": "2026-10-13T01:23:45.000Z",
"updatedAt": "2026-10-13T01:23:45.000Z"
}
```
A new top-level option can be added to the ad products that use the targeting, and to their campaigns. See [Effects on Ad Products and Campaigns](#effects-on-ad-products-and-campaigns).
**Errors**
| Status | `errorCode` | When |
| - | - | - |
| 404 | `TARGETING_NOT_FOUND` | The targeting doesn't exist, belongs to another project, or isn't a context targeting |
| 404 | `TARGETING_CATEGORY_NOT_FOUND` | `parent` isn't an option of this targeting |
| 400 | `TARGETING_CATEGORY_NOT_SUPPORTED` | One of the combinations above |
| 400 | `TARGETING_CONTEXT_LIMIT_EXCEEDED` | The option or the project would go over its Context ID limit. See [Context ID Rules](#context-id-rules) |
| 400 | `TARGETING_CATEGORY_LIMIT_EXCEEDED` | The targeting would have more than 100,000 options, sub-options included |
| 400 | `TARGETING_COLLECTION_CONTEXT_DUPLICATED` | One of the option group's own Context IDs is already an own Context ID of another option group in the project. See [Constraints](/integrations/context-targeting/option-groups#constraints) |
| 400 | `AD_PRODUCT_TARGETING_POLICY_LIMIT_EXCEEDED` | An ad product would have more than 500 required options. See [Effects on Ad Products and Campaigns](#effects-on-ad-products-and-campaigns) |
| 409 | `TARGETING_CATEGORY_NAME_DUPLICATE` | The name is already used in the targeting, or repeats within the request. `detail.existingIds` holds the IDs of the existing options with that name. For a name that repeats within the request, `detail.duplicateOfIndex` is returned instead |
For all error codes and the error format, see [Error Codes](/integrations/context-targeting/errors#error-codes).
***
## Update an Option
Renames an option, changes its Context IDs, moves it into, out of, or between option groups, or changes whether an option group shows its sub-options to advertisers.
### Request
```http theme={null}
PATCH /context-targeting/category/{categoryId}
```
Requires the `context-targeting:write` scope. The response is `200` with the updated [option object](#option-object).
### Path Parameters
| Parameter | Type | Required | Description |
| - | - | :-: | - |
| `categoryId` | string | Yes | Option ID |
### Request Body
All fields are optional. Send only the fields you want to change. Fields you omit stay as they are.
| Field | Type | Description |
| - | - | - |
| `name` | string | New name. See [Name Rules](/integrations/context-targeting/errors#name-rules) |
| `contexts` | object | Changes to the Context IDs. Use `add`, `remove`, or both, or use `set` alone |
| `contexts.add` | string\[] | Context IDs to add. IDs already in the option are ignored |
| `contexts.remove` | string\[] | Context IDs to remove. IDs not in the option are ignored |
| `contexts.set` | string\[] | Replaces all Context IDs of the option with this list |
| `parent` | string or null | Regular options only. An option group ID moves the option under that option group. `null` detaches the option from its option group to the top level. See [Move or Detach an Option](/integrations/context-targeting/option-groups#move-or-detach-an-option) |
| `exposeChildren` | boolean | Option group only. Shows sub-options to advertisers |
These rules apply to the body:
* `contexts` must include `add`, `remove`, or `set`, and `set` can't be combined with `add` or `remove`. Otherwise the request fails with `400`.
* `parent` can't be combined with `contexts`. Move or detach an option in one request and change its Context IDs in another. `parent` can be combined with `name`. If the move fails, the name doesn't change either.
* Only `parent` accepts `null`. `null` in `name`, `contexts`, `exposeChildren`, `add`, `remove`, or `set` fails with `400`. To leave a field unchanged, omit it.
* An option group can't be moved. `parent` on an option group fails with `400 TARGETING_CATEGORY_NOT_SUPPORTED`, and so does `exposeChildren` on a regular option.
* For an option group, `contexts` changes its own Context IDs. Removing all own Context IDs of an option group that has no sub-options fails with `400 TARGETING_CATEGORY_NOT_SUPPORTED`.
* `name`, `contexts`, and `exposeChildren` are written together. If the Context ID change fails, the name and `exposeChildren` don't change. If you send both `add` and `remove`, `add` is applied first, so the added Context IDs stay even if `remove` fails.
You can rename a sub-option and change its Context IDs in the same way as any regular option. To choose between `add`/`remove` and `set`, see [Updating Context IDs](#updating-context-ids).
### Example Request
```bash theme={null}
curl -X PATCH https://lake.adrop.io/context-targeting/category/01JAB3XK5RA1B2C3D4E5F6G7H9 \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"contexts": {
"add": ["science"],
"remove": ["mobile"]
}
}'
```
### Example Response
```json theme={null}
{
"_id": "01JAB3XK5RA1B2C3D4E5F6G7H9",
"name": "Tech",
"type": "atomic",
"contextCount": 3,
"createdAt": "2026-10-13T01:23:45.000Z",
"updatedAt": "2026-10-13T02:10:00.000Z"
}
```
**Errors**
| Status | `errorCode` | When |
| - | - | - |
| 404 | `TARGETING_CATEGORY_NOT_FOUND` | The option doesn't exist in this project's context targetings, or `parent` isn't an option of the same targeting |
| 400 | `TARGETING_CATEGORY_NOT_SUPPORTED` | One of the cases above, `parent` isn't a top-level option group, or the move would leave an option group with no sub-options and no Context IDs of its own |
| 400 | `TARGETING_CATEGORY_LOCKED` | You're moving a top-level option under an option group while an ad product requires it, or while a campaign that hasn't ended includes or excludes it |
| 400 | `TARGETING_CONTEXT_LIMIT_EXCEEDED` | The option or the project would go over its Context ID limit |
| 400 | `TARGETING_COLLECTION_CONTEXT_DUPLICATED` | A Context ID added to an option group is already an own Context ID of another option group in the project |
| 400 | `AD_PRODUCT_TARGETING_POLICY_LIMIT_EXCEEDED` | Detaching the option would give an ad product more than 500 required options |
| 409 | `TARGETING_CATEGORY_NAME_DUPLICATE` | The new name is already used in the targeting. `detail.existingIds` holds the IDs of the options with that name |
***
## Delete an Option
Deletes an option and its Context IDs.
* Deleting an option group promotes its sub-options to the top level, and they take over the option group's settings in ad products and campaigns. If the option group had Context IDs of its own, they stop matching. See [Delete an option group](/integrations/context-targeting/option-groups#delete-an-option-group).
* You can't delete the last sub-option of an option group that has no Context IDs of its own. Delete the option group instead.
Deleting an option isn't blocked when campaigns use it. Check [Effects on Ad Products and Campaigns](#effects-on-ad-products-and-campaigns) before you delete an option.
### Request
```http theme={null}
DELETE /context-targeting/category/{categoryId}
```
Requires the `context-targeting:write` scope. The response is `200` with the ID of the deleted option.
### Path Parameters
| Parameter | Type | Required | Description |
| - | - | :-: | - |
| `categoryId` | string | Yes | Option ID |
### Example Request
```bash theme={null}
curl -X DELETE https://lake.adrop.io/context-targeting/category/01JAB3XK5RA1B2C3D4E5F6G7H9 \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json"
```
### Example Response
```json theme={null}
{
"_id": "01JAB3XK5RA1B2C3D4E5F6G7H9"
}
```
**Errors**
| Status | `errorCode` | When |
| - | - | - |
| 404 | `TARGETING_CATEGORY_NOT_FOUND` | The option doesn't exist in this project's context targetings. If you resent a delete, the first request already deleted it |
| 400 | `TARGETING_CATEGORY_NOT_SUPPORTED` | The option is the last sub-option of an option group that has no Context IDs of its own |
| 400 | `AD_PRODUCT_TARGETING_POLICY_LIMIT_EXCEEDED` | Deleting the option group would give an ad product more than 500 required options |
***
## Bulk Operations
The bulk endpoints add, update, or delete up to 5,000 options in one request. They require the `context-targeting:write` scope.
* Unless the request fails as a whole, each bulk endpoint returns `200` with a result for each item, in request order. Check `success` on each result. A failed item has an `error`. See [Bulk Responses](/integrations/context-targeting/errors#bulk-responses).
* A request with an invalid body fails as a whole with `400`, even if only one item is invalid. Examples are a Context ID that breaks the [Context ID Rules](#context-id-rules), `set` combined with `add`, or a field that isn't accepted.
* API writes for a project are processed one at a time. Send bulk requests one after another, not in parallel. See [Concurrent Writes](/integrations/context-targeting/errors#concurrent-writes).
Each request must stay within these limits:
| Limit | Add | Update | Delete |
| - | - | - | - |
| Items per request | 1–5,000 | 1–5,000 | 1–5,000 |
| Options created per request, sub-options included | 5,000 | – | – |
| Context IDs per request, all items combined | 10,000 | 10,000 | – |
| Option group items per request | 150 | 150 | 150 |
Option group items are:
* **Add**: items that create an option group (`type: "collection"` or `children`) and items with `parent`.
* **Update**: items with `parent`, `null` included.
* **Delete**: IDs of option groups and sub-options.
A request that breaks a limit fails as a whole with `400`, and nothing in it is applied. If a delete request has more than 150 option groups and sub-options, the error is `TARGETING_CATEGORY_TREE_ITEMS_LIMIT_EXCEEDED`, with the limit in `detail.limit` and the number of option groups and sub-options in your request in `detail.count`. To upload a large set, such as tens of thousands of options, send requests of up to 5,000 items one after another.
### Add Options in Bulk
Adds options to existing targetings. Each item can add to a different targeting.
#### Request
```http theme={null}
POST /context-targeting/category/bulk/create
```
#### Request Body
| Field | Type | Required | Description |
| - | - | :-: | - |
| `items` | object\[] | Yes | Options to add. Each item takes the fields of the [Add an Option](#add-an-option) request body |
| `items[].targeting` | string | Yes | ID of the targeting to add the option to |
A successful item has the new [option object](#option-object) in `data`.
#### Example Request
```bash theme={null}
curl -X POST https://lake.adrop.io/context-targeting/category/bulk/create \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"items": [
{ "targeting": "01JAB3XK5Q8V7M2N4P6R9T0W1Y", "name": "Entertainment", "contexts": ["entertainment", "movie"] },
{ "targeting": "01JAB3XK5Q8V7M2N4P6R9T0W1Y", "name": "Business", "contexts": ["business", "economy"] },
{ "targeting": "01JAB3XK5Q8V7M2N4P6R9T0W1Y", "name": "Sports", "contexts": ["sport"] }
]
}'
```
#### Example Response
The targeting already has an option named "Sports", so only the third item fails.
```json theme={null}
{
"results": [
{
"index": 0,
"success": true,
"data": {
"_id": "01JAB3XK5VD1E2F3G4H5J6K7M8",
"name": "Entertainment",
"type": "atomic",
"contextCount": 2,
"createdAt": "2026-10-13T02:20:00.000Z",
"updatedAt": "2026-10-13T02:20:00.000Z"
}
},
{
"index": 1,
"success": true,
"data": {
"_id": "01JAB3XK5VD1E2F3G4H5J6K7M9",
"name": "Business",
"type": "atomic",
"contextCount": 2,
"createdAt": "2026-10-13T02:20:00.000Z",
"updatedAt": "2026-10-13T02:20:00.000Z"
}
},
{
"index": 2,
"success": false,
"error": {
"statusCode": 409,
"errorCode": "TARGETING_CATEGORY_NAME_DUPLICATE",
"detail": {
"existingIds": ["01JAB3XK5RA1B2C3D4E5F6G7H8"]
}
}
}
],
"succeeded": 2,
"failed": 1
}
```
### Update Options in Bulk
Updates options. Each item can change a different option.
#### Request
```http theme={null}
POST /context-targeting/category/bulk/update
```
#### Request Body
| Field | Type | Required | Description |
| - | - | :-: | - |
| `items` | object\[] | Yes | Updates. Each item takes the fields of the [Update an Option](#update-an-option) request body |
| `items[]._id` | string | Yes | ID of the option to update |
* Each `_id` can appear only once per request. A repeated `_id` fails the whole request with `400`, so put all changes for one option into one item.
* Items are applied separately and can run at the same time. Results are still returned in request order.
* A name that an earlier item frees can be used by a later item in the same request.
A successful item has the updated [option object](#option-object) in `data`.
#### Example Request
```bash theme={null}
curl -X POST https://lake.adrop.io/context-targeting/category/bulk/update \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"items": [
{ "_id": "01JAB3XK5RA1B2C3D4E5F6G7H8", "contexts": { "add": ["basketball"] } },
{ "_id": "01JAB3XK5RA1B2C3D4E5F6G7H9", "name": "Technology" }
]
}'
```
#### Example Response
```json theme={null}
{
"results": [
{
"index": 0,
"success": true,
"data": {
"_id": "01JAB3XK5RA1B2C3D4E5F6G7H8",
"name": "Sports",
"type": "atomic",
"contextCount": 4,
"createdAt": "2026-10-13T01:23:45.000Z",
"updatedAt": "2026-10-13T02:10:00.000Z"
}
},
{
"index": 1,
"success": true,
"data": {
"_id": "01JAB3XK5RA1B2C3D4E5F6G7H9",
"name": "Technology",
"type": "atomic",
"contextCount": 3,
"createdAt": "2026-10-13T01:23:45.000Z",
"updatedAt": "2026-10-13T02:10:00.000Z"
}
}
],
"succeeded": 2,
"failed": 0
}
```
### Delete Options in Bulk
Deletes options. The same rules as [Delete an Option](#delete-an-option) apply to each ID.
Don't delete an option group and its sub-options in the same request. Option groups and sub-options are processed in request order. If the option group comes first, its sub-options are promoted to the top level and then deleted as top-level options. If the sub-options come first and the option group has no Context IDs of its own, deleting the last sub-option fails and it stays.
#### Request
```http theme={null}
POST /context-targeting/category/bulk/delete
```
#### Request Body
| Field | Type | Required | Description |
| - | - | :-: | - |
| `ids` | string\[] | Yes | IDs of the options to delete |
A successful item has the ID of the deleted option in `data`.
#### Example Request
```bash theme={null}
curl -X POST https://lake.adrop.io/context-targeting/category/bulk/delete \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"ids": ["01JAB3XK5RA1B2C3D4E5F6G7H8", "01JAB3XK5RA1B2C3D4E5F6G7H9"]
}'
```
#### Example Response
```json theme={null}
{
"results": [
{ "index": 0, "success": true, "data": { "_id": "01JAB3XK5RA1B2C3D4E5F6G7H8" } },
{ "index": 1, "success": true, "data": { "_id": "01JAB3XK5RA1B2C3D4E5F6G7H9" } }
],
"succeeded": 2,
"failed": 0
}
```
***
## Context ID Rules
These rules apply to every Context ID you send, in `contexts`, `children`, `add`, `remove`, and `set`.
| Rule | Description |
| - | - |
| Whitespace | Leading and trailing whitespace is trimmed, so `" sport "` and `sport` are the same Context ID. Spaces inside a Context ID are kept |
| Length | 1–256 bytes in UTF-8, after trimming. An empty or whitespace-only value isn't allowed |
| Commas | Not allowed |
| Characters | Any other characters are allowed, but the text must be well-formed Unicode. Unpaired surrogates aren't allowed |
| Case | Case-sensitive. `Sport` and `sport` are different Context IDs. Register them exactly as your pages send them in ad requests |
| Duplicates | Saved once. Duplicates in a request are merged, and adding a Context ID that the option already has changes nothing |
If any Context ID in a request breaks these rules, the whole request fails with `400`.
The same Context ID can be in more than one option. A request with that Context ID matches all of those options. The exception is an option group's own Context IDs, which can't be an own Context ID of another option group in the project. See [Constraints](/integrations/context-targeting/option-groups#constraints).
Context IDs are also limited by count and size:
| Limit | Value |
| - | - |
| Context IDs per option | 50,000. For an option group, this counts its own Context IDs |
| Total size per option | 8 MB, the sum of the Context IDs in UTF-8 |
| Context IDs per project | 200,000, summed over all options in all context targetings of the project. A Context ID in two options counts twice |
| Context IDs per request | 10,000, all items and `add`, `remove`, and `set` combined |
A change that would put an option or the project over its limit fails with `400 TARGETING_CONTEXT_LIMIT_EXCEEDED`. These limits are checked only when a change makes an option or the project larger, so a change that only removes Context IDs is never blocked. A request with more than 10,000 Context IDs fails as a whole with `400`. For all limits, see [Limits](/integrations/context-targeting/errors#limits).
***
## Updating Context IDs
Change Context IDs with `add`/`remove` or with `set` in [Update an Option](#update-an-option) or [Update Options in Bulk](#update-options-in-bulk).
| Method | How it works | Use it when |
| - | - | - |
| `add` / `remove` | Changes only the Context IDs you list | You get changes as they happen, such as a section being added or removed |
| `set` | Replaces all Context IDs of the option with your list | You have the full list for the option, and it has 10,000 Context IDs or fewer |
`add`, `remove`, and `set` give the same result if you send the same request again, so you can resend them after a timeout. See [Retries and Duplicate Requests](/integrations/context-targeting/errors#retries-and-duplicate-requests).
**More than 10,000 Context IDs**
A request can carry up to 10,000 Context IDs, so `set` can't replace a longer list in one request. Sync the option this way instead:
1. Read the option's current Context IDs with [Get an Option](#get-an-option), paging with `offset` and `limit`.
2. Compare them with your list.
3. Send only the differences with `add` and `remove`, up to 10,000 Context IDs per request, one request after another.
Don't split a full list into several `set` requests. Each `set` replaces all Context IDs, so a later `set` drops the Context IDs of an earlier one. Even one `set` followed by `add` requests leaves the option with only part of its list between requests, and ad serving can pick up that partial list.
Changes reach ad serving after a short delay. To check whether a change has been loaded, see [When Changes Take Effect](/integrations/context-targeting/overview#when-changes-take-effect).
***
## Effects on Ad Products and Campaigns
Ad products and campaigns select options, so changing an option can change where campaigns serve. Adding or removing Context IDs changes the pages where campaigns that include or exclude the option can serve.
**New top-level options**
When you add a top-level option, either a regular option or an option group, ad products that have settings for the targeting also get the option:
* If an ad product sets every current top-level option of the targeting to **Required Include**, the new option is also set to **Required Include**, and the product's campaigns that haven't ended include it too. **Required Exclude** works the same way.
* Otherwise, the new option is added to the ad product as an option advertisers can select. This is the case when some top-level options aren't required, or when they mix **Required Include** and **Required Exclude**. Campaigns don't change.
So for an ad product that requires every option, a new option widens where its campaigns serve (**Required Include**) or narrows it (**Required Exclude**). Once one top-level option of the targeting isn't required in the ad product, later new options are added as selectable options too. To require them, change the ad product's targeting in the console. See [Selling Targeting](/targeting/sell).
A sub-option added to an option group isn't added to ad products by itself. Instead, the option group also matches the sub-option's Context IDs. See [Add an Option to an option group](/integrations/context-targeting/option-groups#add-an-option-to-an-option-group).
**Limit on required options**
An ad product can have up to 500 options set to **Required Include** or **Required Exclude**, across all its targetings. If the rule above would put an ad product over this limit, the new top-level options aren't created and fail with `400 AD_PRODUCT_TARGETING_POLICY_LIMIT_EXCEEDED`.
| Field | Description |
| - | - |
| `detail.limit` | Maximum number of required options per ad product |
| `detail.count` | Number of required options the ad product would have after the change. If several ad products go over the limit, the largest number |
| `detail.products` | IDs of the ad products that would go over the limit |
In a bulk request, every new top-level option for that targeting fails, and the other items are applied. Reduce the required options of the ad product in the console, then send the request again. The same limit applies when you delete an option group or detach a sub-option, because the option group's settings are copied to its sub-options.
**Deleting options**
Deleting an option isn't blocked when campaigns use it, unlike [deleting a targeting](/integrations/context-targeting/targetings#delete-a-targeting). The option is removed from the campaigns that select it. If it was the only option from the targeting that a campaign included, the campaign no longer requires any of the targeting's Context IDs. Instead of stopping, it can serve on requests with other Context IDs or with none.
Before you delete an option, check whether campaigns use it. In the console, an option with campaigns booked has the **In Use** status. See [Managing Targeting](/targeting/manage). Change or end those campaigns before you delete the option.
**Excluding Context IDs**
An option only lists the Context IDs to match. There's no condition for "not in this option". To keep ads off certain pages, put the Context IDs of those pages in an option and exclude the option in the ad product or campaign, for example with **Required Exclude** on the ad product. Don't build an option that lists every other Context ID.
# Context Targeting API
Source: https://docs.adrop.io/integrations/context-targeting/overview
Manage context targetings, targeting options, and Context IDs from your server with an API key.
The Context Targeting API lets you manage context targeting from your server. Map the sections, boards, or tags in your CMS to targeting options and Context IDs, and update them automatically as your content changes instead of editing them by hand in the console.
The API works on the same data as context targeting in the console. Targetings you create with the API appear in the console, and the API can read targetings created in the console.
***
## Prerequisites
* An API key with both the `context-targeting:read` and `context-targeting:write` scopes. A project owner creates the key in **\[Settings]** > **\[Integrations]** and selects both scopes. See [Integrations & API Keys](/integrations/overview) for how to create a key.
* You can't change the scopes of an existing key. If your current key doesn't have both scopes, create a new key, and revoke the old one if you no longer need it.
Grant both scopes, even if you mostly write. Each endpoint checks a single scope, and `context-targeting:write` doesn't include `context-targeting:read`. A key with only the write scope gets `403` on every `GET` request, so it can't look up IDs, read the current Context IDs, or check whether a change has been loaded.
Call the API only from your server. Don't include the API key in apps or web pages.
***
## Basic Information
| Item | Value |
| - | - |
| Base URL | `https://lake.adrop.io` |
| Authentication | API key in the `x-api-key` header. The key determines the project, so paths don't include a project ID. |
| Content-Type | `application/json` |
| Max request body | 8 MB. A larger request returns `413`. |
| ID format | 26-character ULID issued by the server, such as `01JAB3XK5Q8V7M2N4P6R9T0W1Y`. An ID that isn't a valid ULID returns `400`. |
| Write processing | The API processes writes for a project one at a time. A write sent while another write is in progress can fail with `409 CONTEXT_TARGETING_WRITE_IN_PROGRESS`, so send writes one after another, not in parallel. Reads aren't affected. See [Concurrent Writes](/integrations/context-targeting/errors#concurrent-writes). |
For all request limits, see [Limits](/integrations/context-targeting/errors#limits).
***
## Terms
Some API paths and fields use different names from the console. These docs use the console's terms.
| API | Console term | Description |
| - | - | - |
| `targeting` | Targeting | A context targeting, such as "News Sections". The API handles context targetings only. |
| `category` | Targeting option (option) | An option in a targeting, such as "Sports". |
| `contexts` | Context ID | The IDs registered to an option, such as `sport`. Your app or website sends them as `contextId` in ad requests. |
| `type: "collection"` | Option group | An option that groups sub-options. It matches its own Context IDs and those of its sub-options. A regular option has `type: "atomic"`. |
| `children`, `parent` | Sub-option | An option inside an option group. An option group's `children` lists its sub-options, and a sub-option's `parent` is its option group. |
| `exposeChildren` | Show sub-options to advertisers | Whether advertisers can select each sub-option of an option group individually. It doesn't change ad serving. |
***
## Endpoints
Paths are relative to the base URL. Each endpoint requires the scope shown.
| Method | Path | Scope | Description |
| - | - | - | - |
| `GET` | `/context-targeting` | `context-targeting:read` | [List targetings](/integrations/context-targeting/targetings#list-targetings) |
| `GET` | `/context-targeting/{targetingId}` | `context-targeting:read` | [Get a targeting](/integrations/context-targeting/targetings#get-a-targeting) and its options |
| `POST` | `/context-targeting` | `context-targeting:write` | [Create a targeting](/integrations/context-targeting/targetings#create-a-targeting), optionally with options |
| `PATCH` | `/context-targeting/{targetingId}` | `context-targeting:write` | [Rename a targeting](/integrations/context-targeting/targetings#rename-a-targeting) |
| `DELETE` | `/context-targeting/{targetingId}` | `context-targeting:write` | [Delete a targeting](/integrations/context-targeting/targetings#delete-a-targeting) and all of its options |
| `POST` | `/context-targeting/bulk/create` | `context-targeting:write` | [Create targetings in bulk](/integrations/context-targeting/targetings#create-targetings-in-bulk), up to 100 per request |
| `POST` | `/context-targeting/bulk/update` | `context-targeting:write` | [Rename targetings in bulk](/integrations/context-targeting/targetings#rename-targetings-in-bulk), up to 100 per request |
| `POST` | `/context-targeting/bulk/delete` | `context-targeting:write` | [Delete targetings in bulk](/integrations/context-targeting/targetings#delete-targetings-in-bulk), up to 100 per request |
| `GET` | `/context-targeting/{targetingId}/categories` | `context-targeting:read` | [List the options](/integrations/context-targeting/options#list-options) of a targeting, page by page |
| `GET` | `/context-targeting/category/{categoryId}` | `context-targeting:read` | [Get an option](/integrations/context-targeting/options#get-an-option) with its Context IDs |
| `POST` | `/context-targeting/{targetingId}/category` | `context-targeting:write` | [Add an option](/integrations/context-targeting/options#add-an-option) to a targeting |
| `PATCH` | `/context-targeting/category/{categoryId}` | `context-targeting:write` | [Update an option](/integrations/context-targeting/options#update-an-option): rename it, change its Context IDs, move it, or set `exposeChildren` |
| `DELETE` | `/context-targeting/category/{categoryId}` | `context-targeting:write` | [Delete an option](/integrations/context-targeting/options#delete-an-option) |
| `POST` | `/context-targeting/category/bulk/create` | `context-targeting:write` | [Add options in bulk](/integrations/context-targeting/options#add-options-in-bulk), up to 5,000 per request |
| `POST` | `/context-targeting/category/bulk/update` | `context-targeting:write` | [Update options in bulk](/integrations/context-targeting/options#update-options-in-bulk), up to 5,000 per request |
| `POST` | `/context-targeting/category/bulk/delete` | `context-targeting:write` | [Delete options in bulk](/integrations/context-targeting/options#delete-options-in-bulk), up to 5,000 per request |
Bulk endpoints return a result for each item. See [Bulk Responses](/integrations/context-targeting/errors#bulk-responses).
***
## Quick Start
This example creates a "News Sections" targeting, changes the Context IDs of one option, and checks when the change has been loaded.
Create the targeting and its options in one request.
```bash theme={null}
curl -X POST https://lake.adrop.io/context-targeting \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "News Sections",
"categories": [
{ "name": "Sports", "contexts": ["sport", "baseball", "soccer"] },
{ "name": "Tech", "contexts": ["tech", "ai", "mobile"] }
]
}'
```
The response is `201` with the new targeting. Save the `_id` of the targeting and of each option. You need them to change the targeting later.
```json theme={null}
{
"_id": "01JAB3XK5Q8V7M2N4P6R9T0W1Y",
"name": "News Sections",
"categories": [
{
"_id": "01JAB3XK5RA1B2C3D4E5F6G7H8",
"name": "Sports",
"type": "atomic",
"contextCount": 3,
"createdAt": "2026-10-13T01:23:45.000Z",
"updatedAt": "2026-10-13T01:23:45.000Z"
},
{
"_id": "01JAB3XK5RA1B2C3D4E5F6G7H9",
"name": "Tech",
"type": "atomic",
"contextCount": 3,
"createdAt": "2026-10-13T01:23:45.000Z",
"updatedAt": "2026-10-13T01:23:45.000Z"
}
],
"categoryCount": 2,
"createdAt": "2026-10-13T01:23:45.000Z",
"updatedAt": "2026-10-13T01:23:45.000Z"
}
```
Creating isn't idempotent. If a create request times out, see [Retries and Duplicate Requests](/integrations/context-targeting/errors#retries-and-duplicate-requests) before you send it again.
When your sections change, update the Context IDs of the option. `add` and `remove` change only the IDs you list.
```bash theme={null}
curl -X PATCH https://lake.adrop.io/context-targeting/category/01JAB3XK5RA1B2C3D4E5F6G7H8 \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"contexts": {
"add": ["basketball", "golf"],
"remove": ["soccer"]
}
}'
```
The response is `200` with the updated option.
```json theme={null}
{
"_id": "01JAB3XK5RA1B2C3D4E5F6G7H8",
"name": "Sports",
"type": "atomic",
"contextCount": 4,
"createdAt": "2026-10-13T01:23:45.000Z",
"updatedAt": "2026-10-13T01:30:00.000Z"
}
```
To replace all Context IDs of an option at once, see [Updating Context IDs](/integrations/context-targeting/options#updating-context-ids).
Read the targeting and compare its `syncedAt` with the time your update succeeded.
```bash theme={null}
curl "https://lake.adrop.io/context-targeting/01JAB3XK5Q8V7M2N4P6R9T0W1Y?includeCategories=false" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json"
```
```json theme={null}
{
"_id": "01JAB3XK5Q8V7M2N4P6R9T0W1Y",
"name": "News Sections",
"categoryCount": 2,
"syncedAt": 1791855005000,
"createdAt": "2026-10-13T01:23:45.000Z",
"updatedAt": "2026-10-13T01:30:06.000Z"
}
```
The update in the previous step succeeded at 2026-10-13T01:30:00.000Z (`1791855000000`). `syncedAt` is later than that, so the change has been loaded, and ad serving picks it up within a few more minutes. If `syncedAt` is missing or earlier, read the targeting again after a short wait. See [When Changes Take Effect](#when-changes-take-effect).
Send the Context ID of the page a user is viewing as `contextId` in your ad requests. After the change takes effect, an ad request with `contextId` set to `basketball` matches the "Sports" option.
See [Creating Context Targeting](/targeting/context) for how Context IDs work in ad requests.
***
## When Changes Take Effect
A successful write is saved right away. Adrop then loads the project's context targetings into ad serving, and the change usually reaches ad serving within a few minutes.
Use the `syncedAt` field of a targeting (epoch milliseconds) to check whether a change has been loaded.
* `syncedAt` is the time the most recent load of the targeting started. A change that succeeded at or before `syncedAt` has been loaded, and ad serving picks it up within a few more minutes.
* `syncedAt` is omitted for a new targeting. It's also omitted after you change Context IDs (`add`, `remove`, or `set`), add an option, or move or detach an option. It usually comes back when the next load finishes.
* A load that was already running when you made a change can set `syncedAt` to that load's start time, which is earlier than your change. That load may not include your change, but a later load does.
* Renaming or deleting doesn't clear `syncedAt`.
So don't decide based on whether `syncedAt` is present. Record the time your request succeeded, then read the targeting that contains the change, for example with [Get a Targeting](/integrations/context-targeting/targetings#get-a-targeting) and `includeCategories=false`. The change has been loaded when `syncedAt` is equal to or later than that time. `syncedAt` uses Adrop's server clock, so allow a margin if your clock may differ.
Until a change takes effect, ad requests are matched against the previous Context IDs. Add a new Context ID before your app or website starts sending it in ad requests.
***
## Related Documents
List, create, rename, and delete targetings
Add options and keep their Context IDs up to date
Group options into an option group with sub-options
Error codes, limits, and how to retry
Create and manage API keys
How context targeting and Context IDs work
Connect targetings to ad products and sell them to advertisers
# Targetings
Source: https://docs.adrop.io/integrations/context-targeting/targetings
List, get, create, rename, and delete context targetings with the Context Targeting API.
A targeting holds the targeting options that advertisers choose from when they book a campaign. Use these endpoints to manage targetings. To manage the options inside a targeting, see [Targeting Options](/integrations/context-targeting/options).
* Read endpoints require the `context-targeting:read` scope, and write endpoints require `context-targeting:write`. See [Prerequisites](/integrations/context-targeting/overview#prerequisites).
* Your API key determines the project, so paths don't include a project ID.
* Each endpoint lists only its own errors. For errors that apply to all endpoints, see [Error Codes](/integrations/context-targeting/errors#error-codes).
***
## Targeting Object
The list, get, create, and rename endpoints return targetings in the following format. Delete endpoints return only `_id`. In API fields, options are called `categories`.
| Field | Type | Description |
| - | - | - |
| `_id` | string | Targeting ID (26-character ULID). The server generates it. |
| `name` | string | Targeting name |
| `categories` | object\[] | All options in the targeting, including option groups and their sub-options. Each item is an [option object](/integrations/context-targeting/options#option-object) that has the number of Context IDs (`contextCount`) but not the Context IDs themselves. |
| `categoryCount` | number | Number of options in the targeting, including sub-options. Always included. |
| `syncedAt` | number | Time the most recent load of this targeting into ad serving started, in epoch milliseconds. Changes that succeeded at or before this time have been loaded. |
| `createdAt` | string | Time the targeting was created (ISO 8601, UTC) |
| `updatedAt` | string | Time the targeting was last updated (ISO 8601, UTC). It also changes when the targeting is loaded into ad serving. |
`categories` and `syncedAt` aren't included in every response:
| Response | `categories` | `syncedAt` |
| - | - | - |
| [List Targetings](#list-targetings), [Get a Targeting](#get-a-targeting) | Included by default. Omitted with `includeCategories=false`. | Included when available |
| [Create a Targeting](#create-a-targeting), [Create Targetings in Bulk](#create-targetings-in-bulk) | Included | Omitted, because a new targeting hasn't been loaded yet |
| [Rename a Targeting](#rename-a-targeting), [Rename Targetings in Bulk](#rename-targetings-in-bulk) | Omitted | Included when available |
`syncedAt` is omitted until the targeting's first load finishes. After you change Context IDs, add an option, or move or detach an option, it's usually omitted until the next load finishes.
To check whether a change has been loaded, compare the time the change succeeded with `syncedAt`. Don't rely on whether `syncedAt` is present. See [When Changes Take Effect](/integrations/context-targeting/overview#when-changes-take-effect).
***
## List Targetings
Returns all context targetings in your project. Audience targetings aren't included, and the list isn't paginated.
### Request
```http theme={null}
GET /context-targeting
```
Requires the `context-targeting:read` scope.
### Query Parameters
| Parameter | Type | Required | Default | Description |
| - | - | :-: | - | - |
| `includeCategories` | boolean | No | `true` | Whether to include `categories`. With `false`, `categories` is omitted, and `categoryCount` still shows the number of options. Use `false` when your targetings have many options, and read the options page by page with [List Options](/integrations/context-targeting/options#list-options). Values other than `true` and `false` return `400`. |
### Example Request
```bash theme={null}
curl "https://lake.adrop.io/context-targeting?includeCategories=false" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json"
```
### Example Response
`200` with an array of [targeting objects](#targeting-object):
```json theme={null}
[
{
"_id": "01JAB3XK5Q8V7M2N4P6R9T0W1Y",
"name": "News Sections",
"categoryCount": 2,
"syncedAt": 1791854650000,
"createdAt": "2026-10-13T01:23:45.000Z",
"updatedAt": "2026-10-13T01:24:12.000Z"
}
]
```
***
## Get a Targeting
Returns one context targeting.
### Request
```http theme={null}
GET /context-targeting/{targetingId}
```
Requires the `context-targeting:read` scope.
### Path Parameters
| Parameter | Type | Required | Description |
| - | - | :-: | - |
| `targetingId` | string | Yes | Targeting ID |
### Query Parameters
| Parameter | Type | Required | Default | Description |
| - | - | :-: | - | - |
| `includeCategories` | boolean | No | `true` | Whether to include `categories`. With `false`, `categories` is omitted, and `categoryCount` still shows the number of options. Values other than `true` and `false` return `400`. |
### Example Request
```bash theme={null}
curl "https://lake.adrop.io/context-targeting/01JAB3XK5Q8V7M2N4P6R9T0W1Y" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json"
```
### Example Response
`200` with a [targeting object](#targeting-object):
```json theme={null}
{
"_id": "01JAB3XK5Q8V7M2N4P6R9T0W1Y",
"name": "News Sections",
"categories": [
{
"_id": "01JAB3XK5RA1B2C3D4E5F6G7H8",
"name": "Sports",
"type": "atomic",
"contextCount": 3,
"createdAt": "2026-10-13T01:23:45.000Z",
"updatedAt": "2026-10-13T01:23:45.000Z"
},
{
"_id": "01JAB3XK5RA1B2C3D4E5F6G7H9",
"name": "Tech",
"type": "atomic",
"contextCount": 3,
"createdAt": "2026-10-13T01:23:45.000Z",
"updatedAt": "2026-10-13T01:23:45.000Z"
}
],
"categoryCount": 2,
"syncedAt": 1791854650000,
"createdAt": "2026-10-13T01:23:45.000Z",
"updatedAt": "2026-10-13T01:24:12.000Z"
}
```
To read the Context IDs of an option, use [Get an Option](/integrations/context-targeting/options#get-an-option).
**Errors**
| Status | `errorCode` | When |
| - | - | - |
| 404 | `TARGETING_NOT_FOUND` | Your project has no context targeting with this ID. The ID of an audience targeting also returns this error. |
***
## Create a Targeting
Creates a context targeting. You can create its options in the same request.
### Request
```http theme={null}
POST /context-targeting
```
Requires the `context-targeting:write` scope.
### Request Body
| Field | Type | Required | Default | Description |
| - | - | :-: | - | - |
| `name` | string | Yes | — | Targeting name, 2–36 characters. Must be unique among all targetings in your project, including audience targetings. See [Name Rules](/integrations/context-targeting/errors#name-rules). |
| `categories` | object\[] | No | — | Options to create with the targeting. Up to 100 options per request, counting sub-options. |
Each item in `categories` takes the following fields:
| Field | Type | Required | Default | Description |
| - | - | :-: | - | - |
| `name` | string | Yes | — | Option name, 2–36 characters. Must be unique within the targeting, including option groups and sub-options. |
| `contexts` | string\[] | No | — | Context IDs of the option. For an option group, the option group's own Context IDs. See [Context ID Rules](/integrations/context-targeting/options#context-id-rules). |
| `type` | string | No | See description | `atomic` (a regular option) or `collection`. If omitted, the option is an option group when `children` is present, even as an empty array, and `atomic` otherwise. |
| `children` | object\[] | No | — | Option group only. Sub-options to create, each with `name` (required) and `contexts`. |
| `exposeChildren` | boolean | No | `false` | Option group only. Whether to show sub-options to advertisers. See [Show Sub-options to Advertisers](/integrations/context-targeting/option-groups#show-sub-options-to-advertisers). |
* A request can include up to 10,000 Context IDs in total, including those of sub-options.
* The server generates all IDs. A request that includes `_id` fails with `400`.
* Items in `categories` can't include `parent`. A request that includes it fails with `400`. To add an option to an existing option group, see [Add an Option to an option group](/integrations/context-targeting/option-groups#add-an-option-to-an-option-group).
* To create more than 100 options, create the targeting first, and then use [Add Options in Bulk](/integrations/context-targeting/options#add-options-in-bulk).
For how option groups work, see [Create an option group](/integrations/context-targeting/option-groups#create-an-option-group).
### Example Request
```bash theme={null}
curl -X POST https://lake.adrop.io/context-targeting \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "News Sections",
"categories": [
{ "name": "Sports", "contexts": ["sport", "baseball", "soccer"] },
{ "name": "Tech", "contexts": ["tech", "ai", "mobile"] }
]
}'
```
### Example Response
`201` with the new [targeting object](#targeting-object), including its options:
```json theme={null}
{
"_id": "01JAB3XK5Q8V7M2N4P6R9T0W1Y",
"name": "News Sections",
"categories": [
{
"_id": "01JAB3XK5RA1B2C3D4E5F6G7H8",
"name": "Sports",
"type": "atomic",
"contextCount": 3,
"createdAt": "2026-10-13T01:23:45.000Z",
"updatedAt": "2026-10-13T01:23:45.000Z"
},
{
"_id": "01JAB3XK5RA1B2C3D4E5F6G7H9",
"name": "Tech",
"type": "atomic",
"contextCount": 3,
"createdAt": "2026-10-13T01:23:45.000Z",
"updatedAt": "2026-10-13T01:23:45.000Z"
}
],
"categoryCount": 2,
"createdAt": "2026-10-13T01:23:45.000Z",
"updatedAt": "2026-10-13T01:23:45.000Z"
}
```
Save the `_id` of the targeting and of each option. You need them to change the targeting later.
**Errors**
| Status | `errorCode` | When |
| - | - | - |
| 409 | `TARGETING_NAME_DUPLICATE` | A targeting in your project already uses `name`. If it's a context targeting, `detail.existingId` is its ID. Otherwise, `detail` is omitted. |
| 409 | `TARGETING_CATEGORY_NAME_DUPLICATE` | Two options in `categories`, including sub-options, have the same name. |
| 400 | `TARGETING_CATEGORY_NOT_SUPPORTED` | An option's fields don't match its type. For example, an option group has neither sub-options nor Context IDs of its own, `children` is sent with `type: "atomic"`, or `exposeChildren` is sent for a regular option. The targeting isn't created. |
| 400 | `TARGETING_CONTEXT_LIMIT_EXCEEDED` | The project would exceed its total number of Context IDs. See [Limits](/integrations/context-targeting/errors#limits). |
| 400 | `TARGETING_COLLECTION_CONTEXT_DUPLICATED` | An option group's own Context ID is already an own Context ID of another option group in the project or in the same request. `detail.conflicts` lists the duplicates. See [Constraints](/integrations/context-targeting/option-groups#constraints). |
If the name is already in use, the response looks like this:
```json theme={null}
{
"statusCode": 409,
"errorCode": "TARGETING_NAME_DUPLICATE",
"detail": {
"existingId": "01JAB3XK5Q8V7M2N4P6R9T0W1Y"
}
}
```
Creating isn't idempotent. If a create request times out or fails with a `5xx` error, you can send it again. If the first request had succeeded, the resend fails with `409 TARGETING_NAME_DUPLICATE`, and `detail.existingId` is the ID of the targeting that the first request created. See [Retries and Duplicate Requests](/integrations/context-targeting/errors#retries-and-duplicate-requests).
***
## Rename a Targeting
Changes the name of a targeting. Its options and Context IDs don't change.
### Request
```http theme={null}
PATCH /context-targeting/{targetingId}
```
Requires the `context-targeting:write` scope.
### Path Parameters
| Parameter | Type | Required | Description |
| - | - | :-: | - |
| `targetingId` | string | Yes | Targeting ID |
### Request Body
| Field | Type | Required | Description |
| - | - | :-: | - |
| `name` | string | Yes | New targeting name, 2–36 characters. Must be unique among all targetings in your project, including audience targetings. You can send the current name. See [Name Rules](/integrations/context-targeting/errors#name-rules). |
### Example Request
```bash theme={null}
curl -X PATCH https://lake.adrop.io/context-targeting/01JAB3XK5Q8V7M2N4P6R9T0W1Y \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Article Sections" }'
```
### Example Response
`200` with the renamed [targeting object](#targeting-object), without `categories`. To read its options, use [Get a Targeting](#get-a-targeting) or [List Options](/integrations/context-targeting/options#list-options).
```json theme={null}
{
"_id": "01JAB3XK5Q8V7M2N4P6R9T0W1Y",
"name": "Article Sections",
"categoryCount": 2,
"syncedAt": 1791854650000,
"createdAt": "2026-10-13T01:23:45.000Z",
"updatedAt": "2026-10-13T02:10:00.000Z"
}
```
**Errors**
| Status | `errorCode` | When |
| - | - | - |
| 404 | `TARGETING_NOT_FOUND` | Your project has no context targeting with this ID. |
| 409 | `TARGETING_NAME_DUPLICATE` | Another targeting in your project already uses `name`. If it's a context targeting, `detail.existingId` is its ID. |
***
## Delete a Targeting
Deletes a targeting and all of its options, including option groups and sub-options.
You can't delete a targeting while an approved campaign that hasn't ended includes or excludes any of its options. The request fails with `400 TARGETING_LOCKED`, and nothing is deleted. Deleting individual options isn't blocked this way. See [Delete an Option](/integrations/context-targeting/options#delete-an-option).
### Request
```http theme={null}
DELETE /context-targeting/{targetingId}
```
Requires the `context-targeting:write` scope.
### Path Parameters
| Parameter | Type | Required | Description |
| - | - | :-: | - |
| `targetingId` | string | Yes | ID of the targeting to delete |
### Example Request
```bash theme={null}
curl -X DELETE https://lake.adrop.io/context-targeting/01JAB3XK5Q8V7M2N4P6R9T0W1Y \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json"
```
### Example Response
`200` with only the ID of the deleted targeting:
```json theme={null}
{
"_id": "01JAB3XK5Q8V7M2N4P6R9T0W1Y"
}
```
**Errors**
| Status | `errorCode` | When |
| - | - | - |
| 404 | `TARGETING_NOT_FOUND` | Your project has no context targeting with this ID. If you resent a delete request that had succeeded, the targeting is already deleted. |
| 400 | `TARGETING_LOCKED` | An approved campaign that hasn't ended includes or excludes one of the targeting's options. |
***
## Bulk Operations
Create, rename, or delete up to 100 targetings in one request.
* The request is checked as a whole first: the number of items (1–100), the format of each field, and the totals per request. If the check fails, the whole request fails with `400`, and nothing is applied.
* If another API write for your project is in progress, the whole request can fail with `409 CONTEXT_TARGETING_WRITE_IN_PROGRESS`, and nothing is applied. See [Concurrent Writes](/integrations/context-targeting/errors#concurrent-writes).
* Otherwise, the response is `200` with a result for each item, even if every item fails. Some items can succeed while others fail, so check `success` for each item.
| Field | Type | Description |
| - | - | - |
| `results` | object\[] | One result per item, in request order |
| `results[].index` | number | Position of the item in the request's `items` or `ids`, starting at 0 |
| `results[].success` | boolean | Whether the item succeeded |
| `results[].data` | object | Result of a successful item. Omitted for a failed item. |
| `results[].error` | object | Error of a failed item: `statusCode`, `errorCode`, and, for some errors, `detail`. Omitted for a successful item. |
| `succeeded` | number | Number of items that succeeded |
| `failed` | number | Number of items that failed |
Create and delete requests apply the items that pass their checks together. If an unexpected error occurs, nothing in the request is applied: items that failed their checks keep their errors, and the other items fail with `500 UNCATCHED`. Send the same request again. Rename requests apply each item separately, in request order. For details, see [Bulk Responses](/integrations/context-targeting/errors#bulk-responses).
### Create Targetings in Bulk
Creates up to 100 targetings, each with or without options.
#### Request
```http theme={null}
POST /context-targeting/bulk/create
```
Requires the `context-targeting:write` scope.
#### Request Body
| Field | Type | Required | Description |
| - | - | :-: | - |
| `items` | object\[] | Yes | Targetings to create, 1–100. Each item takes the same fields as the [Create a Targeting](#create-a-targeting) request body. |
Across all items, a request can include up to 100 options, counting sub-options, and up to 10,000 Context IDs.
#### Example Request
```bash theme={null}
curl -X POST https://lake.adrop.io/context-targeting/bulk/create \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"name": "Video Sections",
"categories": [
{ "name": "Highlights", "contexts": ["highlight", "replay"] }
]
},
{
"name": "Podcast Sections",
"categories": [
{ "name": "Interviews", "contexts": ["interview", "talk"] }
]
},
{ "name": "News Sections" }
]
}'
```
#### Example Response
The `data` of a successful item is the new [targeting object](#targeting-object), including its options. In this example, the third item fails because a targeting named "News Sections" already exists.
```json theme={null}
{
"results": [
{
"index": 0,
"success": true,
"data": {
"_id": "01JAB3XK5WE1F2G3H4J5K6M7N8",
"name": "Video Sections",
"categories": [
{
"_id": "01JAB3XK5XF1G2H3J4K5M6N7P8",
"name": "Highlights",
"type": "atomic",
"contextCount": 2,
"createdAt": "2026-10-13T02:20:00.000Z",
"updatedAt": "2026-10-13T02:20:00.000Z"
}
],
"categoryCount": 1,
"createdAt": "2026-10-13T02:20:00.000Z",
"updatedAt": "2026-10-13T02:20:00.000Z"
}
},
{
"index": 1,
"success": true,
"data": {
"_id": "01JAB3XK5WE1F2G3H4J5K6M7N9",
"name": "Podcast Sections",
"categories": [
{
"_id": "01JAB3XK5XF1G2H3J4K5M6N7P9",
"name": "Interviews",
"type": "atomic",
"contextCount": 2,
"createdAt": "2026-10-13T02:20:00.000Z",
"updatedAt": "2026-10-13T02:20:00.000Z"
}
],
"categoryCount": 1,
"createdAt": "2026-10-13T02:20:00.000Z",
"updatedAt": "2026-10-13T02:20:00.000Z"
}
},
{
"index": 2,
"success": false,
"error": {
"statusCode": 409,
"errorCode": "TARGETING_NAME_DUPLICATE",
"detail": {
"existingId": "01JAB3XK5Q8V7M2N4P6R9T0W1Y"
}
}
}
],
"succeeded": 2,
"failed": 1
}
```
Items fail with the same errors as [Create a Targeting](#create-a-targeting). When the name belongs to a targeting that an earlier item in the same request created, `TARGETING_NAME_DUPLICATE` has `detail.duplicateOfIndex`, the index of that item, instead of `detail.existingId`.
### Rename Targetings in Bulk
Renames up to 100 targetings.
#### Request
```http theme={null}
POST /context-targeting/bulk/update
```
Requires the `context-targeting:write` scope.
#### Request Body
| Field | Type | Required | Description |
| - | - | :-: | - |
| `items` | object\[] | Yes | Targetings to rename, 1–100 |
| `items[]._id` | string | Yes | Targeting ID |
| `items[].name` | string | Yes | New targeting name. Follows the same rules as [Rename a Targeting](#rename-a-targeting). |
#### Example Request
```bash theme={null}
curl -X POST https://lake.adrop.io/context-targeting/bulk/update \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"items": [
{ "_id": "01JAB3XK5Q8V7M2N4P6R9T0W1Y", "name": "Article Sections" }
]
}'
```
#### Example Response
The `data` of a successful item is the renamed [targeting object](#targeting-object), without `categories`.
```json theme={null}
{
"results": [
{
"index": 0,
"success": true,
"data": {
"_id": "01JAB3XK5Q8V7M2N4P6R9T0W1Y",
"name": "Article Sections",
"categoryCount": 2,
"syncedAt": 1791854650000,
"createdAt": "2026-10-13T01:23:45.000Z",
"updatedAt": "2026-10-13T02:10:00.000Z"
}
}
],
"succeeded": 1,
"failed": 0
}
```
Items fail with the same errors as [Rename a Targeting](#rename-a-targeting).
### Delete Targetings in Bulk
Deletes up to 100 targetings and all of their options.
#### Request
```http theme={null}
POST /context-targeting/bulk/delete
```
Requires the `context-targeting:write` scope.
#### Request Body
| Field | Type | Required | Description |
| - | - | :-: | - |
| `ids` | string\[] | Yes | IDs of the targetings to delete, 1–100 |
#### Example Request
```bash theme={null}
curl -X POST https://lake.adrop.io/context-targeting/bulk/delete \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"ids": ["01JAB3XK5Q8V7M2N4P6R9T0W1Y"]
}'
```
#### Example Response
The `data` of a successful item has only the `_id` of the deleted targeting. In this example, an approved campaign that hasn't ended includes one of the targeting's options, so the targeting isn't deleted.
```json theme={null}
{
"results": [
{
"index": 0,
"success": false,
"error": {
"statusCode": 400,
"errorCode": "TARGETING_LOCKED"
}
}
],
"succeeded": 0,
"failed": 1
}
```
Items fail with the same errors as [Delete a Targeting](#delete-a-targeting).
# Integrations & API Keys
Source: https://docs.adrop.io/integrations/overview
Generate and manage API keys and configure external integrations from [Settings] > [Integrations] in the console.
## Overview
The **\[Settings]** > **\[Integrations]** menu is where you configure all external Adrop integrations.
* **API Keys**: used for Open API calls and for decrypting rewarded ad SSV callbacks
* **Reward Ad SSV**: configure the server URL that receives reward-completion callbacks — see [Reward Ad SSV](/integrations/ssv)
The Integrations menu is accessible only to project members with the **owner** role.
***
## Generating an API Key
Go to **\[Settings]** > **\[Integrations]** in the console.
In the **API Keys** section, click **\[Create API Key]** and fill in the following:
| Field | Description |
| - | - |
| **Key Name** | A name to identify the key's purpose (e.g., "Report Integration"). Must be 1–100 characters. |
| **Scope** | Select at least one scope. Calls are limited to the granted scopes. Scopes cannot be changed after the key is created. |
Each project can have up to 10 active API keys. If you have reached the limit, revoke a key you no longer use, then create a new one.
Copy the generated API key and store it securely.
The API key is shown **only once** at creation. It cannot be retrieved afterward, so make sure to copy and store it securely.
### Available Scopes
| Scope | Description |
| - | - |
| `report:read` | Query campaign reports and backfill revenue data |
| `context-targeting:read` | Read context targetings, targeting options, and Context IDs |
| `context-targeting:write` | Create, update, and delete context targetings, targeting options, and Context IDs |
| `campaign:read` | Read information on booked campaigns |
| `campaign:subscribe` | Register, read, and delete the campaign booking webhook |
The Open APIs currently available are the [Report API](/integrations/reports), the [Context Targeting API](/integrations/context-targeting/overview), and the [Campaign Booking Webhook](/integrations/campaign-booking-webhook). To use the Context Targeting API, grant both `context-targeting:read` and `context-targeting:write`. To use the Campaign Booking Webhook, grant both `campaign:read` and `campaign:subscribe`. `context-targeting:read`, `context-targeting:write`, and `campaign:subscribe` aren't selected by default when you create a key, so select them yourself.
***
## Calling the Open API
### Basic Information
| Item | Value |
| - | - |
| Base URL | `https://lake.adrop.io` |
| Protocol | HTTPS |
| Authentication | API Key (`x-api-key` header) |
### Authentication
Include the `x-api-key` header in every API request.
```bash theme={null}
curl -X POST https://lake.adrop.io/report \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{...}'
```
Do not share your API key externally. If a key is exposed, revoke it immediately and generate a new one.
***
## Managing API Keys
### Viewing Keys
View issued API keys in the **API Keys** section of the **Integrations** menu.
| Column | Description |
| - | - |
| **Key Name** | Name entered at creation. Click the key ID shown below the name to copy it (the key ID is not the API key itself). |
| **API Key** | Only the prefix (e.g., `adrop_7kF2`) is shown; the rest is masked |
| **Scope** | Number of granted scopes (hover for full list) |
| **Created** | Key creation date |
### Changing Scopes
You cannot change the scopes of an issued API key. Create a new key with the scopes you need, switch your integrations to the new key, and then revoke the old key.
### Revoking Keys
Revoke unused keys from the row menu using **\[Revoke]**.
Revoked API keys are blocked immediately. API calls using the revoked key will fail. Verify that no active integrations are using the key before revoking.
API keys that are connected to a **Rewarded Ad SSV** cannot be revoked. Change the API key in the SSV or delete the SSV configuration, then try again.
***
## Related
Register the SSV callback URL that receives reward-completion events and decrypt the payload
Query campaign performance and backfill revenue data via API
Manage context targeting options and Context IDs from your server
Receive changes to booked campaigns and look them up
# Report API
Source: https://docs.adrop.io/integrations/reports
Learn how to query campaign performance and backfill revenue data via API.
## Overview
The Report API lets you query performance data by campaign, product, unit, and creative, as well as backfill ad revenue data.
### Prerequisites
* An API key with `report:read` permission is required
* See the [Integrations guide](/integrations/overview) for key generation instructions
### Basic Information
| Item | Value |
| - | - |
| Base URL | `https://lake.adrop.io` |
| Authentication | API key in `x-api-key` header |
| Content-Type | `application/json` |
| Max Query Range | 30 days |
***
## Campaign Report
Query performance data with cross-tabulation by campaign, product, unit, and creative.
### Request
```
POST /report
```
### Request Parameters
| Parameter | Type | Required | Description |
| - | - | :-: | - |
| `tab` | string | Yes | Primary grouping dimension (`campaign`, `product`, `unit`, `creative`) |
| `segment` | string | Yes | Cross-tabulation dimension. Must be a valid combination with `tab`. |
| `startDate` | string | Yes | Start date (YYYY-MM-DD) |
| `endDate` | string | Yes | End date (YYYY-MM-DD). Max 30 days from start date |
| `rollups` | string\[] | No | Time granularity options. `date` adds daily breakdown, `time` adds hourly breakdown |
| `filters` | object\[] | No | Filter results by specific IDs |
### Valid tab × segment Combinations
| tab | Available segments |
| - | - |
| `campaign` | `campaign`, `unit`, `creative` |
| `product` | `campaign`, `product`, `unit`, `creative` |
| `unit` | `campaign`, `product`, `unit`, `creative` |
| `creative` | `unit`, `creative` |
### Filter Format
```json theme={null}
{
"filters": [
{
"field": "campaign",
"value": ["CAMPAIGN_ID_1", "CAMPAIGN_ID_2"]
}
]
}
```
`field` accepts `campaign`, `product`, `unit`, or `creative`. `value` is an array of matching IDs. When multiple filters are set, only data matching ALL conditions is returned.
### Example Request
```bash theme={null}
curl -X POST https://lake.adrop.io/report \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"tab": "campaign",
"segment": "unit",
"startDate": "2026-03-01",
"endDate": "2026-03-07",
"rollups": ["date"]
}'
```
### Response
```json theme={null}
{
"summary": {
"impressions": 125000,
"impressions_d": 100000,
"impressions_v": 25000,
"clicks": 3750,
"clicks_d": 3000,
"clicks_v": 750,
"dismisses": 200,
"ctr": 0.03,
"ctr_d": 0.03,
"ctr_v": 0.03
},
"items": [
{
"tab": "01J...",
"segment": "01J...",
"date_tz": "2026.03.01.",
"id": "01J...",
"title": "Campaign A",
"campaign": "01J...",
"campaign_name": "Campaign A",
"campaign_status": "active",
"unit": "01J...",
"unit_name": "Banner Top",
"impressions": 5000,
"impressions_d": 4000,
"impressions_v": 1000,
"clicks": 150,
"clicks_d": 120,
"clicks_v": 30,
"dismisses": 10,
"ctr": 0.03,
"ctr_d": 0.03,
"ctr_v": 0.03
}
]
}
```
### Response Fields
`summary` contains grand totals, `items` contains individual detail rows.
#### Common Fields
| Field | Type | Description |
| - | - | - |
| `tab` | string | Primary dimension ID |
| `segment` | string | Cross-tabulation dimension ID (when tab ≠ segment) |
| `date_tz` | string | Date (when rollups includes `date`) |
| `hour_tz` | string | Hour range, e.g. "09-10" (when rollups includes `time`) |
| `id` | string | Entity ID |
| `title` | string | Entity name |
#### Performance Metrics
| Field | Type | Description |
| - | - | - |
| `impressions` | number | Total impressions (display + video) |
| `impressions_d` | number | Display impressions |
| `impressions_v` | number | Video impressions |
| `clicks` | number | Total clicks (display + video) |
| `clicks_d` | number | Display clicks |
| `clicks_v` | number | Video clicks |
| `dismisses` | number | Dismiss count |
| `ctr` | number | Click-through rate (clicks / impressions). 0.027 = 2.7% |
| `ctr_d` | number | Display CTR |
| `ctr_v` | number | Video CTR |
#### Campaign Info (when tab or segment is campaign)
| Field | Type | Description |
| - | - | - |
| `campaign` | string | Campaign ID |
| `campaign_name` | string | Campaign name |
| `campaign_status` | string | Campaign status |
| `start_time` | string | Campaign start time |
| `end_time` | string | Campaign end time |
| `target_impressions` | number | Target impressions |
| `target_clicks` | number | Target clicks |
| `advertiser_name` | string | Advertiser name |
| `agency_name` | string | Agency name |
| `agency_email` | string | Agency email |
#### Product/Unit/Creative Info
| Field | Type | Description |
| - | - | - |
| `product` / `product_name` | string | Product ID / name |
| `unit` / `unit_name` | string | Unit ID / name |
| `creative` / `creative_name` | string | Creative ID / name |
| `creative_status` | string | Creative status |
#### Cost/Settlement Info
| Field | Type | Description |
| - | - | - |
| `supply_amount` | number | Supply amount (budget) |
| `refund_amount` | number | Refund amount |
| `commission` | number | Commission |
| `settlement` | number | Final settlement (supply - commission - refund) |
| `payment_type` | string | Payment type (card, bank, etc.) |
| `pricing_type` | string | Pricing type (CPM, CPC, CPP, etc.) |
| `price` | number | Unit price |
| `cost` | number | Actual cost |
| `currency` | string | Currency code (USD, KRW, etc.) |
***
## Backfill Report
Query backfill ad revenue data broken down by unit and date. All monetary values are in USD.
### Request
```
POST /report/backfill
```
### Request Parameters
| Parameter | Type | Required | Description |
| - | - | :-: | - |
| `startDate` | string | Yes | Start date (YYYY-MM-DD) |
| `endDate` | string | Yes | End date (YYYY-MM-DD). Max 30 days from start date |
### Example Request
```bash theme={null}
curl -X POST https://lake.adrop.io/report/backfill \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"startDate": "2026-03-01",
"endDate": "2026-03-07"
}'
```
### Response
```json theme={null}
{
"summary": {
"impressions": 50000,
"clicks": 1500,
"ctr": 0.03,
"cpc": 0.12,
"cpm": 2.5,
"revenue": 125.0,
"showRate": 0.85
},
"items": [
{
"unit": "01J...",
"unit_name": "Banner Top",
"date": "2026-03-01",
"impressions": 8000,
"showRate": 0.85,
"clicks": 240,
"ctr": 0.03,
"cpc": 0.12,
"cpm": 2.5,
"revenue": 20.0
}
]
}
```
### Response Fields
| Field | Type | Description |
| - | - | - |
| `unit` | string | Unit ID |
| `unit_name` | string | Unit name |
| `date` | string | Date (YYYY-MM-DD) |
| `impressions` | number | Impression count |
| `showRate` | number | Show rate (impressions / fills). 0.85 = 85% |
| `clicks` | number | Click count |
| `ctr` | number | Click-through rate. 0.027 = 2.7% |
| `cpc` | number | Cost per click (USD) |
| `cpm` | number | Cost per mille (USD) |
| `revenue` | number | Revenue (USD) |
***
## Error Codes
| HTTP Status | Description |
| - | - |
| `400` | Bad request. Date range exceeds 30 days, invalid tab × segment combination, etc. |
| `401` | API key is missing or invalid |
| `403` | Missing `report:read` permission |
| `502` | Internal service error. Please retry later |
***
## Limitations
* Maximum query range per request is **30 days**.
* Date format must be **YYYY-MM-DD** (ISO 8601).
* Per-key rate limits are not currently enforced but may be introduced in the future.
# Reward Ad SSV
Source: https://docs.adrop.io/integrations/ssv
Register the SSV callback URL that receives reward-completion events for rewarded ads and decrypt the payload.
## Overview
With **Server-Side Verification (SSV)** for rewarded ads, Adrop sends a callback to your server URL when a user completes watching an ad. This lets you verify reward grants on your own server and prevent abuse.
The callback payload is encrypted with your API key, so even if the URL is exposed, only a party holding the API key can decrypt its contents.
***
## Setup
Create an API key first — it will be used to decrypt the SSV callback payload. SSV registration requires **at least one** active API key in the project.
See [Integrations & API Keys](/integrations/overview#generating-an-api-key) for instructions.
Under **\[Settings]** > **\[Integrations]** > **Reward Ad SSV**, click **\[+ Register Reward Ad SSV]**.
| Field | Description |
| - | - |
| **URL** | Server URL to call when a reward is completed. Must start with `https://`. |
| **API key** | API key used to decrypt the callback payload. Defaults to the most recently created active key. |
Set `userId` and `customData` via `setServerSideVerificationOptions()` in the SDK. These values are included in the callback payload.
See the SDK guides for each platform:
* [Android Rewarded — Server-Side Verification](/sdk/android/rewarded#server-side-verification)
* [iOS Rewarded — Server-Side Verification](/sdk/ios/rewarded#server-side-verification)
* [React Native Rewarded — Server-Side Verification](/sdk/react-native/rewarded#server-side-verification)
* [Flutter Rewarded — Server-Side Verification](/sdk/flutter/rewarded#server-side-verification-ssv)
Only HTTPS URLs are accepted at registration/edit time. Private IPs (localhost, 10.x, 172.16–31.x, 192.168.x, 169.254.x) are blocked.
***
## Request Specification
Adrop delivers the callback as follows.
| Item | Value |
| - | - |
| Method | `POST` |
| Content-Type | `application/json` |
| Retries | Up to 3 attempts (0ms / 1s / 2s intervals) |
| Timeout | 5 seconds per attempt |
| Success | HTTP 200 response |
The request body is AES-256-GCM encrypted.
```json theme={null}
{
"encrypted": "::"
}
```
Each segment is a hex string separated by colons (`:`). After decryption, the plaintext JSON has the following shape:
| Field | Type | Description |
| - | - | - |
| `project` | string | Adrop project ID |
| `app` | string | Adrop app ID |
| `unit` | string | Adrop ad unit ID |
| `adNetwork` | string | Ad network identifier |
| `adUnit` | string | Unit identifier within the ad network |
| `userId` | string? | User identifier set via the SDK |
| `customData` | string? | Custom data set via the SDK |
| `rewardItem` | string | Reward item name |
| `rewardAmount` | number | Reward amount |
| `transactionId` | string | Unique transaction ID for deduplication |
| `timestamp` | number | Callback time (Unix ms) |
`transactionId` is unique. If your server receives a `transactionId` that was already processed, handle it idempotently to avoid granting duplicate rewards.
***
## Decrypting the Payload
The AES-256-GCM key is **the raw API key hashed with SHA-256 (32 bytes)**.
### Node.js
```javascript theme={null}
import { createDecipheriv, createHash } from 'node:crypto'
import express from 'express'
function decryptSsvPayload(encrypted, apiKey) {
const [ivHex, tagHex, ciphertextHex] = encrypted.split(':')
const key = createHash('sha256').update(apiKey).digest() // 32 bytes
const decipher = createDecipheriv('aes-256-gcm', key, Buffer.from(ivHex, 'hex'))
decipher.setAuthTag(Buffer.from(tagHex, 'hex'))
const plaintext = Buffer.concat([
decipher.update(Buffer.from(ciphertextHex, 'hex')),
decipher.final()
]).toString('utf8')
return JSON.parse(plaintext)
}
const app = express()
app.post('/ssv-callback', express.json(), (req, res) => {
const payload = decryptSsvPayload(req.body.encrypted, process.env.ADROP_API_KEY)
// Grant rewards based on payload.userId, payload.customData, payload.rewardAmount, etc.
res.sendStatus(200)
})
```
### Python
```python theme={null}
import hashlib
import json
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
def decrypt_ssv_payload(encrypted: str, api_key: str) -> dict:
iv_hex, tag_hex, ciphertext_hex = encrypted.split(':')
key = hashlib.sha256(api_key.encode()).digest() # 32 bytes
aesgcm = AESGCM(key)
# AESGCM expects ciphertext concatenated with the authentication tag
plaintext = aesgcm.decrypt(
bytes.fromhex(iv_hex),
bytes.fromhex(ciphertext_hex) + bytes.fromhex(tag_hex),
None
)
return json.loads(plaintext)
```
SSV callbacks are designed so that only a party holding the Adrop API key can decrypt them, even if the URL is exposed. **If the API key connected to the SSV is leaked**, revoke it immediately on the server side and replace it with a new API key via the Integrations menu.
***
## Editing and Deleting
Use the row menu in the **Reward Ad SSV** section to edit or delete an existing SSV.
* **Edit**: Change the URL or the connected API key.
* **Delete**: Callbacks will no longer be invoked. SDK-side `userId` / `customData` values continue to be stored in Adrop's internal SSV log.
***
## Related
How to generate, manage, and revoke API keys
Query campaign performance and backfill revenue data via API
# Ad.Note Campaign Reservation Terms
Source: https://docs.adrop.io/legal/adnote-campaign-terms
Terms and conditions for advertisers and publishers booking campaigns through Ad.Note
## Article 1 (Purpose)
The purpose of this Agreement is to stipulate the rights, obligations, and related matters between Open Rhapsody Co., Ltd. (hereinafter referred to as the "Company") and the Applicant (hereinafter referred to as the "Advertiser") regarding the selection of advertising media and the use of advertising placement services (hereinafter referred to as the "Ad Campaign") through the advertising platform provided by the Company (Ad Note, hereinafter referred to as the "Platform").
***
## Article 2 (Service Contents)
1. The Company shall support the Advertiser in selecting desired media from multiple advertising media available on the Platform to conduct Ad Campaigns.
2. The Company shall provide additional services necessary for the overall Ad Campaign, such as ad delivery, performance analysis, and operation optimization. However, the Company does not guarantee the results of ad performance.
3. Conditions for ad placement (period, budget, billing method, etc.) shall follow the terms specified by the Advertiser upon selecting media and making a reservation on the Platform.
***
## Article 3 (Advertiser's Obligations)
1. The Advertiser shall pay the advertising fees in accordance with the method and deadline set by the Company.
2. The Advertiser warrants that the ad creatives and related information provided do not infringe upon relevant laws, the Platform's ad policies, or the rights of third parties, including intellectual property rights.
3. The Advertiser must comply with the ad policies and review procedures required by the Publisher (Media Partner). The Company shall not be liable for any issues arising from the violation thereof.
4. Any suspension of ads or underperformance caused by failure to comply with modification requests or policies required by the Publisher shall be the responsibility of the Advertiser.
***
## Article 4 (Company's Obligations)
1. The Company shall support the Advertiser in freely selecting advertising media and smoothly conducting Ad Campaigns through the Platform.
2. The Company shall not use the information provided by the Advertiser for purposes other than service provision and shall securely manage it in accordance with relevant laws.
3. Upon receiving the advertising fees from the Advertiser, the Company shall deduct **20%** of the amount as a Platform Service Fee and settle the remaining amount to the Publisher on a monthly basis. The Platform Service Fee rate is subject to change, and any changes shall be applied after prior notice to the Advertiser and the Publisher.
***
## Article 5 (Payment Methods)
### 5.1. Selection of Payment Method
The Advertiser may select one of the following payment methods on the Platform:
* **Manual Payment:** Post-payment settlement within 5 business days of the beginning of the month following the Ad Campaign end date.
* **Automatic Payment:** Pre-payment of the estimated ad budget at the reservation stage of the Ad Campaign.
### 5.2. Manual Payment
* **Issuance of Settlement Statement:** The Company shall issue a settlement statement to the Advertiser within 2\~3 days of the beginning of the month following the Ad Campaign end date.
* **Issuance of Tax Invoice:** The Company shall issue a tax invoice on the 5th day of the month following the Ad Campaign end date. Details are as follows:
1. **Amount:** Final spent amount of the completed Ad Campaign.
2. **Date:** The last day of the month in which the Ad Campaign ended.
3. **Recipient Email:** Emails of the Advertiser's contract manager and settlement manager.
* **Payment Schedule and Method:**
1. **Due Date:** Payment shall be made within 30 days from the tax invoice issuance date, provided that the deadline may vary upon mutual agreement.
2. **Bank Account:** **KB국민은행 231404-04-333374 (Account Holder: 주식회사 오픈랩소디)**
### 5.3. Automatic Payment
1. The Advertiser must pre-pay the estimated ad budget to proceed with the Ad Campaign.
* The estimated budget is determined based on the campaign goals set by the Advertiser (e.g., media, ad product, period, targeting, etc.).
* The pre-paid budget is consumed (billed) according to the actual progress of the Ad Campaign.
2. **Billing Criteria:**
* **CPC:** Billed based on the actual number of clicks.
* **CPM:** Billed based on the actual number of impressions.
* **CPP:** Billed based on the number of days the ad is executed (execution is counted if there is at least 1 impression per day).
3. **Refund of Unused Budget:**
* After the Ad Campaign ends, any unused amount of the estimated budget shall be refunded to the payment method used by the Advertiser within 3\~5 business days from the end date.
4. The Ad Campaign operates within the pre-paid budget and will not be billed in excess of that budget.
***
## Article 6 (Payment Gateway Service)
1. The Advertiser agrees that payments may be processed through a Payment Gateway (PG) provider (e.g., Toss Payments) contracted by the Company.
2. Any errors or disputes arising during the payment process shall be subject to the terms and conditions of the PG provider, and the Company shall not be liable therefor.
***
## Article 7 (Cancellation and Penalty)
### 7.1. Cancellation Penalties
In the event of Ad Campaign cancellation after the contract is finalized, a penalty shall be charged according to the following criteria:
| Cancellation Timing | Penalty |
| - | - |
| **15 business days or more** before the Start Date | No Penalty |
| **Within 14 business days** before the Start Date | 50% Penalty |
| **Within 7 business days** before the Start Date | 100% Penalty |
| **On or after the Start Date** | 100% Penalty |
### 7.2. Calculation Basis
The penalty is calculated based on the Ad Campaign budget specified in the contract.
### 7.3. Business Days
Business days are defined as Monday through Friday, 09:00 to 18:00, excluding weekends and public holidays.
### 7.4. Penalty Processing
If a penalty is incurred due to cancellation, it shall be processed as follows depending on the payment method:
* **Manual Payment:** The Advertiser must pay the penalty amount to the Company's designated account within 5 business days from the cancellation date.
* **Automatic Payment:** The amount excluding the penalty shall be refunded to the payment method used by the Advertiser after the cancellation approval.
***
## Article 8 (Late Payment Interest)
1. If the Advertiser fails to pay advertising fees, penalties, or other outstanding amounts within the deadline specified in this Agreement, the Advertiser shall pay late payment interest calculated at a rate of **12% per annum** on the delayed amount.
2. Late payment interest is calculated on a daily basis from the day following the payment deadline to the actual payment date and will be added to the invoice for the following month.
***
## Article 9 (Limitation of Liability)
1. The Company shall not be liable for service interruptions or degradation of ad performance caused by reasons attributable to third parties.
2. The Company shall not be liable for issues arising within the scope of the Publisher's responsibility, such as policy changes, review delays, or ad delivery failures, nor for damages caused by the Advertiser's failure to comply with the Publisher's policies and conditions.
3. In the event of a dispute with a third party regarding the ad content provided, the Advertiser shall resolve it at its own responsibility and expense, and the Company shall bear no liability therefor.
***
## Article 10 (Confidentiality)
Neither party shall disclose any confidential information obtained during the execution and performance of this Agreement to any third party.
***
## Article 11 (Termination)
1. Either party may terminate this Agreement by providing 30 days' prior written notice to the other party.
2. Upon termination, the Advertiser must settle all costs incurred and outstanding amounts up to the date of termination.
***
## Article 12 (Intellectual Property Rights)
All intellectual property rights regarding the Platform and related software, technology, and documents belong to the Company. The Advertiser may not reproduce, modify, distribute, or provide them to third parties.
***
## Article 13 (Dispute Resolution)
Any dispute arising in connection with this Agreement shall be resolved by mutual consultation. If not resolved, the Seoul Central District Court shall be the competent court of the first instance.
***
## Article 14 (Miscellaneous)
1. This Agreement becomes effective on the date the Advertiser agrees to the terms on the Platform.
2. Details regarding the Ad Campaign (reservation date, amount, product name, advertiser name, targeting info, etc.) shall follow the content specified in the attached "Ad Campaign Reservation Details."
***
## Attachments
### Attachment 1. Ad Campaign Reservation Details
# Privacy Policy
Source: https://docs.adrop.io/legal/privacy
A.drop Privacy Policy
Open Rhapsody Co., Ltd. (hereinafter referred to as the "Company") values the privacy of its users while operating the A.drop service and complies with all applicable domestic and international laws and regulations, including the Personal Information Protection Act (PIPA) of Korea, GDPR, and CCPA/CPRA.
This Privacy Policy applies to the Company's website, Ad.Control Console, Advertiser Platform, A.drop SDK, and all related services provided by the Company.
This policy was revised on September 18, 2026. Amendments take effect 7 days after the announcement date (or 30 days for changes unfavorable to users).
## Article 1 (Collected Information)
The Company processes information collected from members (customer representatives) and from End-Users through Publishers separately.
### 1.1. Member Information (Customer Representatives)
Information collected directly when Publishers and Advertisers create accounts, enter into service agreements, or submit inquiries.
* **Mandatory Items:** Email address (ID), password, name (or nickname), company name (organization)
* **Automatically Collected:** IP address, cookies, access logs, service usage records, device information (OS, browser type and version)
* **Payment & Settlement (if applicable):** Bank name, account number, account holder name, business registration certificate, credit card information (processed via payment gateway providers)
* **Inquiry Information:** Name, email address, company name, contact number, and inquiry content (collected via the website inquiry form, coffee chat requests, or referral program applications; retained for 12 months after the purpose is achieved, then destroyed)
### 1.2. End-User Information
Information collected indirectly through the A.drop SDK integrated into the Publisher's app/web. The specific scope depends on the permissions requested by the Publisher's app/web and the End-User's consent. If the Publisher sets its own user identifier (e.g., member ID) in the SDK, direct identifiers may be included.
**a. Advertising Identifiers and SDK Identifiers**
* ADID (Android Advertising Identifier), IDFA (iOS Advertising Identifier, subject to ATT approval)
* SDK-generated identifier: An internal identifier (ULID) generated by the A.drop SDK on first execution. Stored per device and used for service quality maintenance, ad frequency capping, and fraud prevention even when advertising identifiers are disabled.
**b. Device and Network Information**
* Device manufacturer, device model, OS version, language settings, timezone
* IP address (incidental to transmission)
* Carrier information (Android only: MCC/MNC codes and carrier name)
* Screen size and resolution, screen orientation, system theme (light/dark)
* User-Agent string
**c. App Information**
* Package name (Android) or Bundle ID (iOS)
* App version, SDK version
* App signing certificate SHA-1 fingerprint (Android only)
* Debug mode status
**d. Ad Interaction Data**
* Ad request, impression, click status and timestamp
* Ad unit identifier (Unit ID)
* Advertiser and campaign identifiers
* Mediation and backfill routing information (name of network passed through, etc.)
**e. Publisher-Provided Properties and Events**
* **Publisher-Provided User ID:** A user identifier set by the Publisher via the SDK's `setUid` API. The Company requires Publishers to use hashed values or arbitrary internal identifiers instead of direct identifiers such as email or phone number. The Publisher bears responsibility if direct identifiers are provided.
* **Demographic Properties:** Age (AGE), date of birth (BIRTH, yyyyMMdd format), and gender (GENDER) set by the Publisher via the SDK's `AdropMetrics.setProperty` API.
* **Custom Properties:** Up to 256 key-value properties defined by the Publisher.
* **Custom Events:** Arbitrary event names and parameters transmitted by the Publisher via the SDK's `sendEvent` API.
**f. Marketing Consent Information**
* End-User marketing consent status and consent timestamp collected by the Publisher through the SDK.
**g. Automatically Collected Data (within Ad Creatives)**
* Cookies and local storage set by ad creatives (web content)
* Interaction data within ad creatives (scroll, touch, etc.)
### 1.3. Company Website Visitor Information
When visiting the Company's website ([adrop.io](http://adrop.io)), cookies, IP address, access date and time, and visit history are collected. The Company uses Google Analytics to analyze website usage. Visitors may refuse this via the methods described in Article 7.
***
## Article 2 (Purpose of Processing)
The Company uses collected information solely for the following purposes.
### 2.1. Service Operation and Management
Management of A.drop integrated accounts, distribution of advertising platforms, revenue settlement and tax processing, customer support (CS), incident response, and system stability.
### 2.2. Ad Optimization and Reporting
Ad performance measurement and analysis (Attribution), frequency capping, daily impression/click management for pacing, ad fee calculation and revenue settlement by pricing model, ad request validation and invalid traffic filtering (per Article 13(7) of the A.drop Terms of Service), fraud detection and prevention, audience targeting, contextual targeting and audience-based personalized ad delivery, and mediation optimization.
### 2.3. Service Improvement
Analysis of access frequency, statistical analysis for feature improvement, and data analysis for new ad product development.
### 2.4. Collection and Use of Behavioral Information
The Company processes behavioral information for personalized ad delivery as follows.
* **Items Collected:** Advertising identifiers (ADID/IDFA), SDK-generated identifier (ULID), ad request/impression/click history, user properties and events provided by the Publisher, contextual information of the placement where ads are shown
* **Method of Collection:** Automatically collected via the A.drop SDK integrated into the Publisher's app/web
* **Purpose:** Personalized ad delivery, frequency capping, performance measurement, and fraud detection
* **Retention Period:** Per Article 3(2)
* **User Control:** Users may refuse the collection of advertising identifiers and personalized ads via device settings as described in Article 7, and may use the consent withdrawal mechanisms provided by individual app/web services.
***
## Article 3 (Retention Period, Usage, and Destruction)
The Company destroys personal information without delay once the purpose of collection and use has been achieved.
### 3.1. Member Information Retention
**a. Retention per Company Policy**
* **Upon Member Withdrawal:** Retained for **3** months to prevent fraudulent use and respond to disputes, then destroyed
* **Service Usage Logs (access IP, access time):** Retained for **12** months per internal policy, then destroyed
**b. Retention per Relevant Laws**
* Records on contracts or withdrawal of subscription: **5** years (Act on Consumer Protection in Electronic Commerce)
* Records on payment and supply of goods: **5** years (Act on Consumer Protection in Electronic Commerce)
* Records on consumer complaints or dispute settlement: **3** years (Act on Consumer Protection in Electronic Commerce)
* Records on books and supporting documents: **5** years (Framework Act on National Taxes, Value-Added Tax Act)
### 3.2. End-User Data Retention
| Data Type | Retention Period | Basis |
| - | - | - |
| Advertising identifiers (ADID/IDFA) and SDK-generated identifier (ULID) | Up to **13** months (from the last ad interaction date) | Online Behavioral Advertising Privacy Guidelines |
| Ad interaction logs (requests/impressions/clicks) | **13** months | Behavioral information retention period |
| Publisher-provided user ID, demographic properties, custom properties and events | Retained during the contract period with the Publisher, destroyed within **3 months** after contract termination | Processing agreement |
| Marketing consent information | Destroyed without delay upon consent withdrawal | Personal Information Protection Act |
| IP address (access logs) | **3** months | Protection of Communications Secrets Act |
| Cookies and local storage from ad creatives | Same as the lifecycle of the ad creative | - |
### 3.3. Destruction Procedures and Methods
Personal information for which the destruction cause has occurred is destroyed in a manner that makes recovery impossible.
* **Electronic Files:** Permanently deleted using technical methods (e.g., Low-Level Format) that make recovery and reproduction impossible.
* **Paper Documents:** Destroyed by shredding or incineration.
***
## Article 4 (Third-Party Provision and Ad Network Integration)
The Company **does not sell** End-User personal information to external parties for monetary consideration.
However, the minimum data necessary for advertising service provision is transferred to the ad networks below, which may constitute "Sharing for Cross-Context Behavioral Advertising" under CPRA (see Article 6.3). Use for purposes other than those listed below is prohibited.
### 4.1. Bidding Backfill Ads and Mediation Networks
The following networks are included in the Publisher's app only when the Publisher integrates the A.drop SDK's backfill module.
| Service | Operator | Purpose | Items Transferred | Country |
| - | - | - | - | - |
| **AdMob** | Google LLC | Bidding backfill ads and mediation | Advertising identifiers, IP address, device information, ad interaction data | USA |
※ For specific personal information processing by each network, please refer to the respective network's privacy policy.
### 4.2. Rewarded Ad Verification (SSV, Server-Side Verification)
When a rewarded ad view is completed, the userId and customData set by the Publisher in the A.drop SDK are transmitted to Google's server (USA), and Google returns them signed to the Company's server. The Company stores the returned verification data (userId, customData, ad unit ID, transaction ID, reward information) and provides it to the Publisher to determine reward eligibility. The retention period follows Article 3. The Publisher decides what values to set for userId and customData; the Company recommends that no personal information be included in those values.
### 4.3. Ad Player JS Hosting (Automatic Access to External Resources)
When rendering ad creatives, the End-User's device directly connects to the jsDelivr CDN to download JavaScript resources. During this process, the IP address and User-Agent are automatically transmitted to the CDN. jsDelivr is a global CDN that responds from nodes near the user's location; the Company does not directly transfer personal information in this process. This constitutes automatic transmission incidental to network usage, not third-party provision of personal information.
***
## Article 5 (Processing Delegation and International Transfer)
The Company delegates personal information processing tasks to the following vendors for smooth service provision. Due to the global nature of the service, some data may be stored on cloud servers located abroad.
### 5.1. Vendors and Delegated Tasks
| Vendor | Delegated Tasks | Country | Contact |
| - | - | - | - |
| **Amazon Web Services (AWS)** | Cloud infrastructure operation and data storage | USA, Republic of Korea | [aws-korea-privacy@amazon.com](mailto:aws-korea-privacy@amazon.com) |
| **jsDelivr** | JavaScript CDN hosting for ad creatives | USA, EU | [d@jsdelivr.com](mailto:d@jsdelivr.com) |
| **Google LLC** | Website traffic analytics | USA | [googlekrsupport@google.com](mailto:googlekrsupport@google.com) |
In addition to the table above, the Company delegates processing of payment and settlement information to Toss Payments Co., Ltd. (Republic of Korea) and Stripe, Inc. (USA) for Advertiser card payments and Publisher revenue disbursement. Additions or changes to vendors will be reflected in this policy and disclosed.
### 5.2. Sub-Delegation
* If the Company additionally delegates processing to a third party (sub-delegation) beyond the vendors listed in Article 4 and Article 5.1, the Company will obtain the Publisher's prior approval or specify it in the delegation agreement and provide separate notification.
* When vendor changes (additions/replacements) occur, the Company will notify Publishers in advance, and Publishers may object or terminate the delegation agreement.
* The Company imposes the same level of privacy protection obligations on sub-delegates as those in this policy and the delegation agreement.
### 5.3. International Transfer of Personal Information (Overview)
The following protective measures apply to data transferred internationally as described in Article 4 and Article 5.1.
* **Legal Basis:** Article 28-8 of the Personal Information Protection Act, Articles 44-49 of GDPR (EU Standard Contractual Clauses (SCC) or adequacy decisions)
* **Safety Measures:** Encryption in transit (TLS 1.2 or higher), encryption at rest, access control
* **Countries:** As listed in Article 4 and Article 5.1
* **Items Transferred:** As listed in Article 1
* **Transfer Time and Method:** Transmitted via network as needed during service usage
* **Retention Period:** As listed in Article 3
### 5.4. Right to Refuse International Transfer
Users may refuse international transfer by notifying the Company's privacy department ([contact@adrop.io](mailto:contact@adrop.io)) in writing or by email. The Company will notify the result of the processing within 10 days of receiving the refusal request. End-Users may also express their refusal through the relevant app/web service (Publisher). However, refusal of delegation or storage essential for service provision may result in restricted use of the service.
***
## Article 6 (End-User Data and Global Compliance)
Since the Service processes End-User data through the Publisher's app/web, the Company acts as a 'Processor'.
### 6.1. Status as a Processor
* **GDPR / CPRA Roles:** The Publisher is the 'Data Controller' or 'Business' that determines the purpose and means of data collection. The Company is the 'Data Processor' or 'Service Provider' that processes data on behalf of the Publisher.
* **Responsibility for Consent:** The Publisher is solely responsible for obtaining lawful consent from End-Users regarding data collection and use (e.g., for personalized ads). The Company processes only the data transmitted by the Publisher through lawful procedures.
* **Consent Setting Guidance:** After obtaining lawful consent from End-Users for personal information collection/use and personalized ads, the Publisher must convey the result to the SDK's consent gating feature. When consent is set to "not agreed," custom events and user properties (age, date of birth, gender, etc.) are not transmitted to the Company's server. The Publisher bears responsibility for data transmitted while the consent result has not been conveyed.
### 6.2. Exercise of End-User Rights
* End-Users must primarily contact the relevant app/web service (Publisher) to exercise their rights to access, correct, delete, or restrict the processing of their personal information.
* Upon receiving a request for data deletion (Right to Erasure) or restriction of processing from the Publisher, the Company will technically support and fulfill it within 30 days. If the Company receives a request directly from an End-User, it will promptly forward the request to the relevant Publisher, notify the requester, and directly process it within the scope required as a Processor under the Personal Information Protection Act.
### 6.3. CPRA "Sell" and "Share" Measures
* The Company **does not sell** End-User personal information for monetary consideration.
* However, the ad network integration in Article 4.1 may constitute "Sharing for Cross-Context Behavioral Advertising" under CPRA. The Company contractually and technically supports Publishers in providing Opt-Out mechanisms to End-Users.
***
## Article 7 (Management of Cookies and Advertising Identifiers)
The Company uses cookies and advertising identifiers to improve user convenience and provide personalized services. Users may refuse these via device settings.
### 7.1. How to Opt-Out
* **Web:** Browser Settings → Privacy → Block Cookies
* **App (iOS):** Settings → Privacy & Security → Tracking → Turn off "Allow Apps to Request to Track"
* **App (Android):** Settings → Google → Ads → Delete Advertising ID or Opt out of Ads Personalization
***
## Article 8 (Security Measures for Personal Information)
The Company takes the following measures to prevent loss, theft, leakage, forgery, alteration, or damage of users' personal information.
### 8.1. Administrative Measures
Establishment and implementation of internal management plans, regular employee training, and other measures to raise security awareness.
### 8.2. Technical Measures
Management of access rights to personal information processing systems, installation of access control systems, encryption during transmission (TLS 1.2 or higher) and storage (AES-256) of unique identification information, and installation/update of security programs.
### 8.3. Physical Measures
Control of physical access to server rooms and data storage rooms. (The Company complies with AWS data center physical security policies.)
***
## Article 9 (Response to Personal Information Breach Incidents)
### 9.1. Incident Notification and Reporting
If a personal information breach incident occurs, the Company will, within **72** hours of becoming aware of it, (1) report to the Personal Information Protection Commission (or relevant supervisory authority) and (2) notify affected users (members and, under agreements, End-Users) of the following:
* Items of personal information leaked
* Time of the breach and the circumstances
* Measures users can take to minimize potential harm
* Company's response measures and remedy procedures
### 9.2. Notification Duty as a Processor
As a Processor, when the Company becomes aware of a data breach, it will notify the relevant Publisher (data controller) without delay and cooperate with investigations and responses requested by the Publisher.
***
## Article 10 (Data Handling upon Publisher Contract Termination)
Upon termination of a service agreement with a Publisher, the Company follows these procedures.
1. **Choice of Return or Deletion:** Within 30 days from the contract termination date, the Publisher may request the return or complete deletion of End-User data stored by the Company.
2. **Default Handling:** In the absence of a separate request from the Publisher, data is destroyed sequentially according to the retention periods in Article 3.
3. **Export of Publisher-Owned Data:** Upon the Publisher's request, the Company will export data belonging to the Publisher (e.g., campaign information and ad performance reports) in a commonly used file format and provide reasonable transition support.
4. **Destruction Certificate:** Upon the Publisher's request, the Company will provide proof of destruction within 30 days.
5. **Exception:** Information required to be retained by relevant laws is stored separately for the required period, then destroyed.
***
## Article 11 (Personal Information of Children Under 14)
The Company does not directly collect personal information from children under 14. If a Publisher operates an app/web primarily targeting children, the Publisher must obtain consent from legal guardians and apply COPPA (Children's Online Privacy Protection Act) and child-directed service settings in the SDK. The Company processes only the data transmitted by the Publisher through lawful procedures.
***
## Article 12 (User Rights and Exercise (Members))
Members and their representatives may exercise the following rights regarding their personal information.
* **Right to Access:** Request access to member information.
* **Right to Correction and Deletion:** Request correction or deletion of erroneous information, except where retention is required by relevant laws.
* **Right to Restriction:** Request suspension of personal information processing.
* **Exercise Method:** Contact the department listed in Article 13 by email ([contact@adrop.io](mailto:contact@adrop.io)). The Company will take necessary measures within 10 days and reply with the result.
***
## Article 13 (Privacy Officer and Contact Information)
The Company operates a dedicated department to protect user privacy and handle related grievances promptly.
### 13.1. Department and Contact
* **Privacy Officer:** Yusin Kim / CEO / [02-6011-7707](tel:0260117707) / [**contact@adrop.io**](mailto:contact@adrop.io)
* **Department:** A.drop Privacy Team
### 13.2. Remedies for Rights Infringement
For reports or consultations regarding personal information infringement, please contact the following organizations:
* Personal Information Infringement Report Center ([privacy.kisa.or.kr](http://privacy.kisa.or.kr) / 118)
* Supreme Prosecutors' Office Cyber Investigation Division ([spo.go.kr](http://spo.go.kr) / 1301)
* Korean National Police Agency Cyber Investigation Bureau ([ecrm.cyber.go.kr](http://ecrm.cyber.go.kr) / 182)
***
## Article 14 (Changes and Notification of Policy)
When this policy is changed, the Company will announce the reason, content, announcement date, and effective date on the Company's website. For changes unfavorable to users, notice will be provided at least 30 days before the effective date.
# Referral Program Privacy Policy
Source: https://docs.adrop.io/legal/referral-program-privacy
Ad.Control Referral Program Privacy Policy
In operating the Ad.Control Referral Program (hereinafter the "Program"), Open Rhapsody Co., Ltd. (hereinafter the "Company") collects and uses personal information as set out below.
The Referrer has the right to refuse consent to the collection and use of personal information, and where consent to the required items is refused, participation in the Program may be restricted. The Referred Contact may, after being contacted by the Company, request access to, correction of, deletion of, or suspension of the processing of their personal information, as well as cessation of further contact.
This Program is intended for adults (aged 19 or older) for business or work purposes, and does not collect the personal information of children under the age of 14.
## 1. Collection and Use of the Referrer's Own Personal Information
| Items Collected | Purpose of Collection and Use | Retention and Use Period |
| - | - | - |
| \[Required] Name, affiliation, email, phone number, relationship with the referred company and representative, referral details | Receipt and review of referrals, identification of participants, notification of progress and results, response to inquiries | 1 year after the end of the referral (rejection, lapse, or completion of payment). However, referrals for which a reward has been paid are subject to the tax retention period below |
| \[When a reward arises] Account information, business registration certificate (if applicable) | Payment of rewards and tax processing | 5 years from the end of the taxable year to which the payment date belongs (the evidence retention period under relevant laws such as the Framework Act on National Taxes) |
**Processing of Unique Identifying Information (resident registration number, etc.)** Resident registration numbers are not collected on the basis of consent, and are processed **only where there is a specific statutory basis** such as withholding tax or submission of a payment statement under the Income Tax Act (Article 24-2 of the Personal Information Protection Act). Where other unique identifying information (passport number, etc.) must be collected, separate consent is obtained. Copies of identification documents are not requested as a rule, and where collection is unavoidable, information unnecessary for tax filing (such as the last 7 digits of the resident registration number) is masked before processing.
## 2. Collection and Use of the Referred Contact's (Third Party's) Personal Information
This Program operates by having the Referrer provide the information of the New Client's representative (the Referred Contact).
| Items Collected | Purpose of Collection and Use | Retention and Use Period |
| - | - | - |
| \[Required] The Referred Contact's name, affiliation, email, relationship with the Referrer, referral circumstances | Sending referral emails and proposing meetings to New Clients, consultation on new adoption of Ad.Control | As per the retention period table below |
**Retention Period**
| Category | Retention Period |
| - | - |
| Rejected referrals | 1 year from the rejection date |
| Referrals lapsed due to no reply or no meeting held | 1 year from the lapse date |
| Referrals lapsed due to no revenue after a meeting was held | 1 year from the date eligibility lapses |
| Referrals for which a reward payment arose | 5 years from the end of the taxable year to which the payment date belongs |
| Information of representatives who converted into clients | As per the Company's client/sales information processing policy |
| Opt-out or deletion request cases | Deleted immediately (minimal information may be separately retained to prevent re-contact) |
**Legal Basis for Processing and Notice of Third-Party Collection Source** The Company receives the Referred Contact's personal information from the Referrer (a third party). The Referrer must obtain lawful consent from the Referred Contact before providing the Referred Contact's personal information, and the Company obtains the Referrer's confirmation thereof (checkbox) upon Referral Registration. The Company directly notifies the Referred Contact, in the body of the first referral email sent to them, of the information source, the purpose of processing, the possibility of a reward to the Referrer, and the method for requesting deletion. In addition to securing the Referrer's consent, the Company processes such information on the supplementary basis of the Company's legitimate interest in sourcing new clients (Article 15(1)6 and Article 17(1)2 of the Personal Information Protection Act), to the extent that such interest is manifestly superior to the rights of the data subject. Where a referral email or meeting proposal sent to the Referred Contact constitutes advertising information for commercial purposes under the Network Act, the Company confirms, through the Referrer, that the Referred Contact's consent to receipt has been secured, and provides the opt-out method together in the body of the email.
> This email is sent to you on the introduction of \[Referrer name/affiliation]. The Company has received your name, affiliation, and email for the purpose of operating the referral program and consultation on new adoption of Ad.Control. This matter is an introduction received through the A.drop Referral Program, and the Referrer may receive a reward in accordance with the Program policy. If you do not wish to be contacted, please reply to this email and we will delete the relevant personal information and cease further contact.
The Referred Contact has the right to request access to, correction of, deletion of, or suspension of the processing of their personal information, and upon request the Company processes it without delay.
## 3. Provision of the Referrer's Information to the Referred Contact
By including the Referrer as a CC recipient on the referral email or stating "We are contacting you on the introduction of \[name]," the Company provides the Referrer's information to the Referred Contact.
| Recipient | Items Provided | Purpose of Provision | Retention Period |
| - | - | - | - |
| Representative of the referred client (Referred Contact) | The Referrer's name, affiliation, email, referral circumstances | Verification of the referral channel and proposal of a meeting | As per the Referred Contact's internal policy |
Upon Referral Registration, the Referrer consents to their name, affiliation, and email, as well as the fact of the referral, being notified to the Referred Contact.
## 4. Provision of Personal Information to Third Parties
The Company does not provide personal information externally, except for the provision of the Referrer's information to the Referred Contact specified in Article 3 of this Policy, where there is separate consent from the data subject, where there is a statutory basis, or where there is a lawful request from an investigative or examining agency.
## 5. Entrustment of Personal Information Processing
The Company entrusts the processing of personal information for the operation of the Program as set out below, and upon entrustment specifies the entrusted party and the entrusted work and manages and supervises them.
| Entrusted Party | Entrusted Work |
| - | - |
| Google LLC (Google Workspace) | Sending referral emails, responding to inquiries, data storage |
| Notion Labs, Inc. (Notion) | Management of referrals and milestone tracking |
| Giftishow Biz | Sending the Meeting Bonus (Naver Pay Points) |
| Doan Accounting Corporation | Withholding tax, payment statements, and documentary evidence processing |
## 6. Cross-Border Transfer of Personal Information
The Company entrusts the processing of personal information to overseas operators among the entrusted parties in Article 5 above, and discloses such cross-border transfers in accordance with Article 28-8 of the Personal Information Protection Act as set out below.
| Transferee | Country of Transfer | Items Transferred | Date and Method of Transfer | Purpose of Use | Retention and Use Period | Contact |
| - | - | - | - | - | - | - |
| Google LLC | United States | The Referrer's and Referred Contact's name, affiliation, email, phone number, and other items collected under this Policy | Transferred via the information and communications network at the time of service use | Sending referral emails, responding to inquiries, data storage | Until the entrustment ends or the retention period under this Policy ends | Google Privacy Team ([googlekrsupport@google.com](mailto:googlekrsupport@google.com)) / Guide for Korean residents: [https://policies.google.com/privacy/additional?hl=ko\&gl=kr](https://policies.google.com/privacy/additional?hl=ko\&gl=kr) |
| Notion Labs, Inc. | United States | The Referrer's and Referred Contact's name, affiliation, email, referral details | Transferred via the information and communications network at the time of service use | Management of referrals and milestone tracking | Until the entrustment ends or the retention period under this Policy ends | [privacy@makenotion.com](mailto:privacy@makenotion.com) |
The data subject may refuse the cross-border transfer, but in the case of refusal, the use of services necessary for operating the Program may be restricted, which may make participation difficult.
## 7. Rights of the Data Subject
Referrers and Referred Contacts may exercise the following rights regarding their personal information at any time:
1. Request access to, correction of, deletion of, or suspension of the processing of personal information.
2. Withdraw consent.
3. Request an explanation of, and raise an objection to, automated decision-making (if applicable) (Article 37-2 of the Personal Information Protection Act).
Rights may be exercised by the following methods, and the Company processes them within 10 days of the date the request is received:
* Email: [support@adrop.io](mailto:support@adrop.io)
* Postal mail: Addressed to the Personal Information Protection Officer at the Company's address
## 8. Measures to Ensure the Security of Personal Information
The Company takes the following measures for the secure processing of personal information:
* Restricting access rights to personal information to necessary personnel such as Program operations staff, and managing records of the granting, modification, and revocation of access rights.
* Encrypting files containing personal information or restricting access to them.
* Retaining and inspecting access logs of the personal-information processing system.
* Managing and supervising entrusted parties for personal-information processing.
* Destroying unnecessary materials after the completion of reward payment and tax filing.
## 9. Destruction of Personal Information
The Company destroys without delay personal information for which the retention and use period has elapsed or the purpose of processing has been achieved. However, where it must be retained under relevant laws, it is separately stored for the relevant period. Electronic files are deleted in a manner that makes recovery impossible, and printouts are shredded or incinerated.
## 10. Personal Information Protection Officer and Inquiries
* Personal Information Protection Officer: CEO Yusin Kim
* Department in Charge: A.drop Personal Information Protection Team
* Inquiry Email: [support@adrop.io](mailto:support@adrop.io)
* Contact: 02-420-0906
## 11. Remedies for Infringement of Rights
The data subject may contact the following institutions for relief and consultation regarding personal-information infringement:
* Personal Information Dispute Mediation Committee: (without area code) 1833-6972 / [www.kopico.go.kr](http://www.kopico.go.kr)
* Personal Information Infringement Report Center: (without area code) 118 / [privacy.kisa.or.kr](http://privacy.kisa.or.kr)
* Cyber Investigation Division, Supreme Prosecutors' Office: (without area code) 1301 / [www.spo.go.kr](http://www.spo.go.kr)
* Cyber Investigation Bureau, National Police Agency: (without area code) 182 / [ecrm.police.go.kr](http://ecrm.police.go.kr)
## 12. Changes to the Privacy Policy
This Policy applies from July 1, 2026. Where there is any addition, deletion, or modification of the contents of this Policy, the changes will be announced through the website operated by the Company and the like from before the effective date.
# Referral Program Terms of Service
Source: https://docs.adrop.io/legal/referral-program-terms
Ad.Control Referral Program Terms of Service
## Article 1 (Purpose)
These Terms set out the conditions and procedures for using the Ad.Control Referral Program (hereinafter the "Program") operated by Open Rhapsody Co., Ltd. (hereinafter the "Company"), as well as the rights, obligations, and responsibilities between the Company and participants.
## Article 2 (Definitions)
1. "Referrer" means an individual or corporation that has agreed to these Terms, completed Referral Registration with the Company, and been approved by the Company.
2. "New Client" means a new publisher (on a corporate-entity basis) introduced by a Referrer through the Program.
3. "Referred Contact" means a representative of the New Client introduced by the Referrer to the Company, whom the Company treats as the recipient of the referral email and the meeting proposal.
4. "Referral Registration" means advance registration through the official form provided by the Company, and the time of registration serves as the reference point for all eligibility determinations.
5. "Referral Email" means the first introductory email (including the Referrer as a CC recipient) that the Company sends to the Referred Contact after Referral Registration is approved.
6. "Valid Reply" means a reply in which the Referred Contact, or a representative of the New Client who received the introduction or forwarding from the Referred Contact, expresses an intention to hold a meeting, use Ad.Control, or proceed with a related consultation; automatic responses, read receipts, mere expressions of thanks, and explicit refusals are not deemed Valid Replies.
7. "First Meeting Held" means a face-to-face or online meeting actually conducted, with a schedule arranged in advance, between the Company and the working-level representative or decision-maker of the Referred Contact or the New Client. An online meeting means a meeting conducted through a real-time conferencing tool such as Zoom, Google Meet, or Microsoft Teams; a mere phone call is not deemed a meeting regardless of whether it was scheduled in advance, and email correspondence, requests for materials, or scheduling coordination alone are likewise not deemed a meeting.
8. "Settlement-Basis Revenue" collectively refers to the following revenues attributable to the Company arising from a New Client, and means the confirmed amount reflecting deductions for invalid traffic, refunds, ad-spend adjustments, payment gateway fees, accounts receivable, payment cancellations, and other settlement-related deductions. Settlement-Basis Revenue is based on the supply value excluding value-added tax (VAT); where a client has been billed with VAT included, the amount equivalent to such VAT is not included in the Settlement-Basis Revenue.
* a. **Wave Usage Fee Revenue**: the impression-based usage fee paid to the Company by a New Client under the Ad.Control Wave paid plan ([Terms of Service](/legal/terms), Article 2.2).
* b. **Backfill Operating Commission Revenue**: the 20% operating commission received by the Company on the New Client's network ad revenue ([Terms of Service](/legal/terms), Article 8.3). It is based on the commission attributable to the Company, not the total ad revenue.
* c. Under these Terms, "use of Ad.Control" or "operation of Ad.Control" includes both use of the Wave paid plan and Backfill operation, unless otherwise specified.
9. "First Revenue Date" means the date on which Settlement-Basis Revenue first arises from a New Client. Among items (a) and (b) of Article 2(8), the date on which revenue attributable to the Company of KRW 1 or more first arises in the actual operating environment is deemed the First Revenue Date. Once a First Revenue Date is recognized under either item, the Revenue Measurement Period does not restart even if separate revenue subsequently arises under the other item.
10. "Revenue Measurement Period" means the total of three calendar months consisting of the month containing the First Revenue Date and the following two months. (Example: even if the first revenue arises on January 1 or January 31, the measurement period is January, February, and March, and the first month may include only part of the period.)
11. "Completion of the Revenue Measurement Period" means that the New Client maintains Ad.Control in the actual operating environment until the end date of the Revenue Measurement Period. Where the Company reasonably determines that there has been termination, suspension of operation, abnormal or nominal deployment, test-purpose operation, or a substantially non-operational state, the measurement period is not deemed completed.
12. "Meeting Bonus" means the reward paid upon the First Meeting Held.
13. "Revenue Share" means the reward paid based on the Settlement-Basis Revenue.
14. "Business day" means a day excluding Saturdays, Sundays, and public holidays in the Republic of Korea.
## Article 3 (Eligibility and Scope of Application)
1. This Program is operated for Referrers in the Republic of Korea and domestic New Clients. Referrers located overseas or referrals of overseas clients are excluded from rewards except where separately approved by the Company.
2. The Company may restrict participation in consideration of business needs, statutory restrictions, conflicts of interest, concerns about misconduct, existing contractual relationships, and the like.
3. The following persons may not participate in this Program:
* A public official, etc. as defined under Article 2(2) of the Improper Solicitation and Graft Act, whose participation in this Program may be in violation of that Act.
* Any other person prohibited from participating in this Program or receiving rewards under the internal rules of their affiliated institution or company, or under relevant laws.
4. The Referrer warrants, upon applying to participate, that none of the restriction grounds in paragraph 3 apply, and shall compensate the Company for any damages arising from a false warranty.
## Article 4 (Program Participation and Referral Registration)
1. A person wishing to participate in the Program must register the referral in advance through the official form provided by the Company; referrals asserted after the fact are not recognized.
2. Upon Referral Registration, the Referrer must accurately enter their own information (name, affiliation, email, phone number), the Referred Contact's information (name, affiliation, email), the relationship with the Referred Contact, and the date and method by which consent to be contacted was obtained.
3. Upon Referral Registration, the Referrer warrants the following:
* That the Referrer has notified the Referred Contact in advance, and lawfully obtained their consent, regarding the provision of the Referred Contact's personal information (name, email, affiliation, position, etc.) to the Company, the Company's collection and use of such information for sales contact (sending the Referral Email, proposing meetings, providing service information, etc.), and that the retention period of the information is three years from the end date of this Program or the date the referral lapses.
* That the Referrer has notified the Referred Contact that this matter is an introduction through the referral program and that the Referrer may receive a reward.
* That the contents of the Referral Registration are true, and that referral eligibility and rewards may be canceled if there is any falsehood, exaggeration, or omission.
* That participation in this Program and receipt of rewards do not violate the Referrer's affiliated company, clients, business counterparties, or relevant laws or internal rules.
4. The Referrer must maintain accurate contact and payment information during the period of participation in the Program, and the Company shall not be liable for any delay in notification or payment caused by changes to, errors in, or non-response regarding such information.
5. The Company shall notify approval or rejection within five business days of receiving the Referral Registration. A registration may be rejected for reasons such as duplication with an existing pipeline, ineligibility, or potential conflict of interest, and the reason shall be notified together with any rejection.
6. A Referrer wishing to withdraw from the Program may express their intention to withdraw to the Company in writing (including by email). Withdrawal does not affect any reward claims that have already arisen, and no new Referral Registration is possible after withdrawal.
## Article 5 (Recognition and Exclusion of Referrals)
1. A referral is not recognized in any of the following cases:
* Where, as of the registration date, the Company has, within the most recent six months, engaged in two-way contact, a meeting, a proposal, a quotation, a contract, an integration, ad operation, or settlement discussions with the relevant client. However, where a matter for which there was no Valid Reply or follow-up progress from the client for 60 days or more after a past contact is reconnected through the Referrer's substantive introduction, it may be recognized at the Company's discretion.
* Where an officer or employee, agent, advisor, stakeholder who may influence purchasing or contracting decisions, or a specially related person thereof of the New Client refers their own company. However, this may be excepted where the Company has approved it in advance, having determined that there is no possibility of a conflict of interest.
* A person who performs New Client sourcing under a separate sales, partnership, agency, consulting, lead-generation, or similar contractual relationship with the Company (except where approved in advance by the Company).
* Where an officer or employee of the Company makes the referral.
2. Where multiple Referrers register for the same client, the Referrer who registered first takes priority. If the first-registered referral lapses by exceeding the deadlines in Article 6, the opportunity transfers to the next registrant. If there is no next registrant, the Company treats the relevant client as unregistered.
3. Where, after Referral Registration, the client is acquired through its own channel rather than the referral channel, the Company verifies whether the Referred Contact was aware of and went through the Referrer's introduction, and recognizes the referral only where such fact is confirmed.
4. The reward unit is the corporate entity, and revenue from multiple publishers operated by the same corporate entity is aggregated within the Revenue Measurement Period; even if the same corporate entity integrates additional publishers after the end of the measurement period, there is no additional reward. Where a determination of the reward unit is necessary due to a change of trade name, merger, division, business transfer, transfer among affiliates, or the like of a New Client, the Company makes a reasonable determination by comprehensively considering substantive identity, the settlement entity, the operating publisher, the revenue-attribution relationship, and the like.
## Article 6 (Rewards and Conditions for Maintaining Eligibility)
1. Meeting Bonus: Where a first meeting is held, the Company pays the Referrer Naver Pay Points worth KRW 50,000. The Company reasonably determines whether to proceed with a meeting in consideration of client suitability, duplication with an existing pipeline, the Referred Contact's authority and relationship, whether the referral is spammy or nominal, business reasonableness, and the like, and payment is made only where an actual meeting is held. The Meeting Bonus is paid only once per New Client, and is not paid in duplicate even if the same corporate entity, service, or representative, or a substantially identical referral, is registered separately.
2. Validity requirements for a referral: All of the following requirements must be satisfied: (i) a Valid Reply must be confirmed within seven business days of the date the Referral Email is sent; and (ii) the first meeting must actually be held within 30 days of the Valid Reply date. However, with respect to (ii), where the schedule has been confirmed but the actual date held exceeds 30 days due to the client's circumstances or the like, the Company may extend that period at its reasonable discretion. If any of the above requirements is not met, the relevant referral automatically lapses, neither the Meeting Bonus nor the Revenue Share is paid, and new registration by another Referrer for the same client becomes possible.
3. Revenue Share: 50% of the Settlement-Basis Revenue (Article 2(8)) arising within the New Client's Revenue Measurement Period is paid, and the total payment per New Client (corporate entity) is capped at KRW 10,000,000. All reward amounts are based on the amount before withholding tax. The Revenue Share of a corporate or individual-business Referrer is subject to VAT as consideration for introduction or brokerage services (regardless of whether the New Client's revenue is zero-rated); in this case, unless otherwise notified, the KRW 10,000,000 cap is based on the supply value excluding VAT, and the VAT is paid separately from the reward.
4. Additional conditions for Revenue Share payment: For referrals that satisfy paragraph 2, the Revenue Share is paid where all of the following are satisfied:
* The first revenue arises within 180 days of the First Meeting Held date.
* The New Client completes the Revenue Measurement Period (Article 2(11)). In the case of termination, suspension of operation, or non-operation before the end of the measurement period, the Revenue Share is not paid (the Meeting Bonus is retained).
* Where any one of the above stages exceeds its specified deadline, Revenue Share eligibility lapses, and new registration by another Referrer for the same client may become possible.
5. The basis for calculating the Revenue Share is the Settlement-Basis Revenue (Article 2(8)) arising from the New Client. Ad revenue brokered by the Company through Ad.Note is excluded.
6. The Company may selectively decide, in consideration of business reasonableness, client suitability, potential revenue contribution, and the like, whether to send the Referral Email, respond to replies, or proceed with a meeting for each individual referral; where the Company does not proceed with subsequent steps based on such judgment, no reward obligation whatsoever—including the Meeting Bonus and the Revenue Share—arises. Confirmation of a Valid Reply under paragraph 2 means only that the referral's timing requirement is satisfied, and does not give rise to any obligation of the Company to respond further or to proceed with a meeting.
## Article 7 (Settlement and Payment)
1. The Meeting Bonus is paid within 30 days of the last day of the month containing the meeting date.
2. The Revenue Share is paid to the account designated by the Referrer on the regular payment date following confirmation of the Settlement-Basis Revenue through settlement with the client, ad network, and the Company's internal settlement after the end of the Revenue Measurement Period. The regular payment date is within 30 days of the last day of the final month of the Revenue Measurement Period, and payment may be withheld or adjusted where there are reasonable grounds such as settlement delays, deductions for invalid traffic, refunds, accounts receivable, or network adjustments.
3. Upon payment, the Company shares the Settlement-Basis Revenue and the reward calculation results with the Referrer. However, the client's raw data, advertiser names, campaign details, publisher traffic, and the like are not shared, and the shared details are subject to the confidentiality obligation in Article 8.
4. The Revenue Share is based on the amount before withholding tax, and the Company shall deduct withholding tax amounts such as income tax in accordance with relevant laws before payment, and may carry out necessary tax processing such as filing a payment statement. The Revenue Share paid to an individual Referrer is processed under the appropriate income classification under relevant laws, such as other income or business income, taking into account the continuity and repetitiveness of the referral activity; in the case of a corporate or individual-business Referrer, the Company may request the issuance of a VAT tax invoice or qualified documentary evidence.
5. Where the Referrer does not provide the requested information within a reasonable period from the date the Company requested the information necessary for payment, or is not reachable, payment may be withheld, and the payment schedule may be deferred to after the next regular payment date.
## Article 8 (Confidentiality)
1. The Referrer must not disclose or share with any third party the name of the New Client, whether the New Client uses Ad.Control, whether and the scale to which revenue arises, settlement details, the contents of discussions between the Company and the client, or the scale of the rewards received.
2. The Referrer may mention the fact of participating in the Program itself, but disclosure that specifies the client name, reward amount, or settlement details, or that combines these, is prohibited.
3. The confidentiality obligation under this Article is maintained for three years even after the end of a referral, completion of reward payment, withdrawal from the Program, or termination of the Program, and information constituting a trade secret is maintained until that information loses its trade-secret status.
4. In the event of a violation of this obligation, the Company may revoke referral eligibility, extinguish unpaid rewards, and claim a refund of amounts already paid.
## Article 9 (Obligations of the Referrer)
1. The Referrer is not an agent or salesperson of the Company and has no authority to make any declaration of intent on behalf of the Company.
2. The Referrer must not engage in the following acts:
* False or exaggerated representations in the Company's name (such as promises guaranteeing revenue).
* Acts that harm third parties, such as indiscriminate spam sending.
* Sales acts that impersonate or cause confusion with the Company's brand.
* Producing or distributing promotional advertising materials, landing pages, mass-sent messages, and the like using the Company's name, logo, trademark, or service materials without the Company's prior written consent.
3. The Referrer may not assign, pledge, or transfer to a third party any reward claim or status under this Program without the Company's prior written consent.
## Article 10 (Misconduct and Clawback)
1. Where misconduct such as false referrals, collusion with the Referred Contact or client, artificial traffic manipulation, conflicts of interest, improper solicitation, or rebates is confirmed, the Company may refuse to pay rewards and claw back rewards already paid, and may permanently bar the relevant Referrer from participating in the Program. Where the Company notifies a clawback, the Referrer must return the relevant amount within 14 days of the notification date, and where damage has arisen to the Company or the client, the Company may separately claim damages.
2. The Company reasonably applies the above measures in consideration of the degree of the violation, whether it was intentional or negligent, the damage arising to the Company or the client, the Referrer's explanation, and the like.
3. Where revenue is retroactively changed due to deductions for invalid traffic, refunds, network settlement adjustments, or the like, the reward is recalculated based on the confirmed Settlement-Basis Revenue, and where a material retroactive adjustment arises after payment, it may be offset against the next payment or clawed back.
## Article 11 (Processing of Personal Information)
1. The Company collects and uses the personal information of Referrers and Referred Contacts for the operation of the Program, and the details are governed by the separate [Referral Program Privacy Policy](/legal/referral-program-privacy).
2. Where the Referrer provides the Referred Contact's personal information, the Referrer is responsible for obtaining lawful consent from the Referred Contact, and where a dispute, damage, sanction, or complaint arises due to the Referrer's fault, the Referrer shall compensate the Company for the damage thereby incurred to the Company.
3. The Company notifies the Referred Contact of the fact of personal-information processing and the method of opting out when sending the Referral Email.
## Article 12 (Modification and Termination of the Program)
1. The Company may, with prior notice, modify the contents of the Program or terminate the Program, and even without terminating the Program, may temporarily suspend receipt of new Referral Registrations or restrict participation for specific periods, channels, or client groups.
2. Where the Company modifies these Terms, it shall announce the modified contents and the effective date through the website operated by the Company and the like, from 7 days (30 days in the case of changes that are unfavorable or material to the Referrer) before the effective date. Where the Referrer does not explicitly express an objection by the effective date of the modified Terms and continues to participate in the Program, the Referrer is deemed to have agreed to the modified Terms, and a Referrer who does not agree to the modification may discontinue participation in the Program.
3. However, referrals already registered and approved at the time of the modification or termination are processed under the existing terms, and the Referrer is not disadvantaged retroactively.
## Article 13 (Disclaimer and Limitation of Liability)
1. The Company does not guarantee the conclusion of a contract with, the use of the service by, or whether or the scale to which revenue arises from the client introduced by the Referrer; whether and the scale to which revenue arises depends on the client's operation.
2. The Company is not liable for disputes arising in the relationship between the Referrer and the Referred Contact or client.
3. Where the Company bears liability for damages to the Referrer in connection with these Terms or the Program, except in the case of the Company's willful misconduct or gross negligence, such liability is limited to the aggregate of the rewards paid or to be paid to the Referrer in connection with the relevant referral. The Company is not liable for damages exceeding ordinary damages, such as special damages, indirect damages, and lost profits, regardless of foreseeability.
4. Where a dispute, claim, damage, sanction, or complaint arises between a third party and the Company due to the Referrer's violation of these Terms or relevant laws, or due to the falsity or inaccuracy of information the Referrer provided to the Company, the Referrer shall resolve it at their own expense and responsibility, and shall compensate the Company for the damage thereby incurred to the Company (including reasonable legal costs).
## Article 14 (Objections and Dispute Resolution)
1. The Referrer may raise an objection within 30 days of the date the Company sent the settlement details to the email address registered by the Referrer or a separately confirmed contact, and upon the lapse of this period the settlement details are finalized.
2. Matters not specified in these Terms or matters on which there is a difference of interpretation are processed in consideration of relevant laws, the purpose of the Program, and trade practices. In the event of a difference of interpretation, the Company decides after hearing the Referrer's opinion, and does not restrict any remedy recognized under relevant laws.
3. Disputes related to these Terms are governed by the laws of the Republic of Korea, and the court of competent jurisdiction for the first instance regarding a dispute shall be the court having jurisdiction over the location of the Company's head office. However, where the Referrer is a consumer under relevant laws such as the Act on the Regulation of Terms and Conditions, the court of competent jurisdiction follows the Civil Procedure Act.
## Article 15 (Severability)
1. Even where part of a provision of these Terms is found to be void or unenforceable under relevant laws or by a court's determination, this does not affect the validity of the remaining provisions, and the part found void or unenforceable is deemed replaced with lawful and valid content that most closely reflects its intent.
***
**Addendum**
These Terms take effect on July 1, 2026.
Inquiries: [support@adrop.io](mailto:support@adrop.io)
# Terms of Service
Source: https://docs.adrop.io/legal/terms
A.drop Terms of Service
## **Article 1(Purpose and Definitions)**
These Terms govern the use of the A.drop service provided by Open Rhapsody Co., Ltd.
① These Terms are intended to define the rights, obligations, responsibilities, terms of use, and procedures between Open Rhapsody Co., Ltd. (hereinafter referred to as the "Company") and Publishers regarding the use of the A.drop service (hereinafter referred to as the "Service") provided by the Company.
② The terms used in these Terms are defined as follows.
| Term | Definition |
| - | - |
| Publisher | A business that displays ads on its own app or website through the Company's ad service. Also referred to as the "User" in these Terms. |
| Advertiser | A business that seeks to deliver its ads on the Publisher's placements. |
| End-User | A person who actually uses the Publisher's app or website. |
| Ad Unit | An area on the Publisher's app or website where ads are displayed. Each area is assigned a unique identifier (Unit ID). |
| Reserved Ad (Direct Deal) | A method in which the Advertiser reserves a specific ad space of the Publisher in advance and delivers designated ads during a set period. This is the default advertising method of Ad.Control. |
| Bidding Backfill Ads | A method that automatically fills empty ad spaces without reserved ads with ads from external ad networks via real-time bidding. This is a supplementary service of Ad.Control. |
| Creative | The advertising materials (images, videos, text, etc.) actually displayed in ad spaces. |
| Image/Text Creative | Creative composed of still images or text. |
| Video Creative | Creative composed of playing videos. |
| Impression | The number of times an ad is actually shown on the End-User's screen. Specific counting criteria are defined in Article 4. |
| Click | The number of times an End-User clicks an ad, triggering a click event. Counted regardless of whether the Advertiser's page actually opens. |
| Campaign | A series of plans set by the Advertiser to deliver specific ads for a specific period. |
| Ad Product | A bundle of ad placements composed by the Publisher as a sales unit, together with pricing model, unit price, and targeting conditions. |
| Targeting | A setting that displays ads only to End-Users meeting specific conditions. |
| Ad Server | A system responsible for storing, delivering, and measuring ad performance. |
| SDK | Company-provided software installed in the Publisher's app or website to load and display ads. |
③ Terms not defined in these Terms follow the meanings established by relevant laws and the Company's individual policies. Otherwise, general trade practices apply.
***
## Article 2(Service Composition and Ad Types)
① The Service consists of Ad.Control (the core service) and supplementary services that Publishers may choose as needed.
| Category | Service | Description |
| - | - | - |
| Core Service | Ad.Control | Ad server solution for delivering and managing reserved ads |
| Supplementary Service | Ad.Note | Service that promotes Publisher ad products to Advertisers and brokers transactions |
| Supplementary Service | Bidding Backfill Ads | Service that fills empty ad spaces without reserved ads with ads from external ad networks |
② The content of each service is as follows.
1. Ad.Control: An ad server solution primarily for reserved ads. Publishers can create/manage ad spaces and view reserved ad delivery status and revenue performance through this service.
2. Ad.Note: A supplementary service that promotes Publisher ad products to Advertisers and brokers transactions. On the Ad.Note advertising platform ([adnote.adrop.io](http://adnote.adrop.io)), Publisher ad products are listed and Advertisers can directly reserve/purchase them. On the Ad.Note community, product brochures and advertising benefits registered by Publishers in the console are displayed. Details are per Article 8.
3. Bidding Backfill Ads: A supplementary service that fills empty ad spaces without reserved ads to earn additional revenue. Details are per Article 9.
③ Supplementary services are provided only to Publishers who apply for them. Ad.Note requires a listing application for ad products per Article 8(3), and Backfill Ads requires an application at the Ad Unit level per Article 9(1). Failure to apply for supplementary services does not restrict Ad.Control's reserved ad delivery.
④ The Company provides the following ad types.
1. **Banner Ads**
* Standard ads displayed in designated ad spaces within an app or website.
2. **Interstitial Ads**
* Full-screen ads that cover the entire app screen, typically displayed at screen transitions.
3. **Rewarded Ads**
* Full-screen ads similar to interstitial ads that grant rewards (e.g., game items, points) to End-Users who watch the ad to completion.
* Only video creatives are used, but Ad.Control fees exceptionally apply at the same rate as image/text creatives. Details are per Article 5(3).
4. **Native Ads**
* Ads that blend naturally into the surrounding content of an app or website.
5. **Popup Ads**
* Ads that appear as separate windows over the app screen. Display position and size follow ad space and creative settings.
* Supports a carousel (slide) feature for browsing multiple campaigns; in this case, each campaign's creative is counted as an individual impression (see Article 4(2)).
6. **Splash Ads**
* Full-screen ads displayed first when an app launches.
* Because splash ads have short display windows at app launch, they pre-download the next ad image and temporarily store it on the End-User's device (retained for up to 7 days).
* Display timing is controlled by the Company's server. If loading takes longer than a set time (approximately 1 second), the ad is automatically skipped and not counted as an impression.
⑤ Supported environments by ad type are as follows.
| Ad Type | Supported Creatives | Android App | iOS App | Flutter App | React Native App | Web |
| - | - | - | - | - | - | - |
| Banner | Image/Video | Supported | Supported | Supported | Supported | Supported |
| Interstitial | Image | Supported | Supported | Supported | Supported | Not supported |
| Rewarded | Video | Supported | Supported | Supported | Supported | Not supported |
| Native | Image/Text/Video | Supported | Supported | Supported | Supported | Supported |
| Popup | Image/Video | Supported | Supported | Supported | Supported | Not supported |
| Splash | Image | Supported | Supported | Supported | Supported | Not supported |
⑥ Regarding the supported environments in the preceding paragraph, the following apply.
1. Splash ads are shown at the moment an app first launches, so they are supported only in apps, not on websites.
2. The table in paragraph 5 is based on the effective date of these Terms. The Company may add or adjust supported environments. Changes that discontinue currently used features will be notified in advance per Article 15.
***
## Article 3(Ad Products, Pricing Models, and Targeting)
① Publishers compose ad products by bundling their Ad Units and directly set pricing model, unit price, targeting conditions, sales period, etc. However, selling ad products on the Ad.Note advertising platform requires a separate listing application and review. Setting a product as public alone does not automatically list it on Ad.Note. Even after listing is complete, only products set as public are displayed on Ad.Note. Details are per Article 8.
② Publishers may choose one of the following pricing models for each ad product. The pricing models below are the calculation method for ad fees paid by Advertisers to Publishers and are separate from the Ad.Control fees (Article 5) paid by Publishers to the Company.
| Pricing Model | Calculation | Description |
| - | - | - |
| CPM | Set amount per 1,000 impressions | Ad fees are calculated based on impressions. |
| CPC | Set amount per click | Ad fees are calculated based on actual clicks. |
| CPP | Fixed amount per specified period | Fees are paid for using a placement for a set period, regardless of impressions/clicks. |
③ The selection of pricing models follows these rules.
1. Certain pricing models may not be selectable due to the characteristics of the ad type. In such cases, only selectable models are displayed in the console.
2. Splash ad products do not support clicks, so they cannot be sold as CPC. When splash ad products are included, CPC cannot be selected in the console.
3. Splash ads pre-download images and display them at the next app launch (Article 2(4)(6)), so the Company's server has limitations in real-time impression control. For CPM, there may be discrepancies between target and actual impressions. Splash ad products are therefore recommended to be sold as CPP (period-based fixed pricing).
④ In-house ads follow these rules.
1. In-house ads are ads that Publishers register directly to promote their own services or content. In-house ads are a type of reserved ad and do not involve Advertiser payments or revenue settlement.
2. In-house ads are delivered as a lower priority when ad spaces are empty due to no reserved ads (CPM/CPC/CPP) from Advertisers. When reserved ads exist, they take priority.
3. In-house ads are delivered before bidding backfill ads.
4. When impressions actually occur, in-house ads are subject to Ad.Control fees (Article 5).
⑤ The order of ad selection is as follows. If no ad exists in a given category, no ad is displayed in that ad space.
1. Ads reserved by Advertisers (CPM/CPC/CPP)
2. In-house ads (when no Advertiser-reserved ads exist)
3. Bidding backfill ads (when no reserved or in-house ads exist, and only for Ad Units with Backfill enabled. See Article 9)
⑥ The Company provides the following targeting.
1. Audience Targeting
* Publishers define targeting attributes (e.g., age group, gender, interests, usage behavior) based on their own user information and link them to ad products.
* Advertisers may select linked targeting attributes as 'Include' or 'Exclude' when reserving. Publishers may designate certain attributes as mandatory.
* Ad products with targeting conditions may have premium pricing set by the Publisher.
* User attribute information used for targeting is transmitted by Publishers via the SDK. Up to 256 attributes may be registered, with each value up to 256 characters.
2. Contextual Targeting
* A method that displays ads matching the content the End-User is currently viewing (e.g., a specific post or product page).
* Publishers transmit contextual information about the current screen when requesting ads, and the Company selects and delivers matching ads.
⑦ Responsibility for obtaining End-User consent for information used in targeting is per Article 11(3).
⑧ In the Ad.Control Console under Settings > Payment Methods, Publishers register payment methods to offer to Advertisers. Only registered methods appear on the Advertiser's campaign reservation screen, and Advertisers select from among them. The following paragraphs apply to campaigns directly reserved by Publishers from Advertisers. Payment methods and fees for campaigns reserved through Ad.Note follow Ad.Note's policies and are not individually set by Publishers (see Article 8).
| Payment Method | Recipient of Ad Fees | Company Fees | Settlement Method |
| - | - | - | - |
| Bank Transfer | Publisher (Advertiser deposits directly to the Publisher's registered bank account) | None | Does not pass through the Company. |
| Card Payment | Company (received via payment gateway) | Payment gateway fees apply | Company deducts fees and pays the Publisher. |
⑨ For bank transfer, the following apply.
1. Ad fees are deposited directly by the Advertiser to the Publisher's account and do not pass through the Company.
2. Tax invoice issuance, deposit confirmation, and receivables management are handled directly by the Publisher.
3. The Company does not charge payment gateway fees for such ad fees and is not the settlement/payment entity.
⑩ For card payment, the Company handles settlement per the following procedure.
1. When the Advertiser pays by card, the Company receives the ad fees via payment gateway.
2. The Company deducts the payment gateway fees per paragraph 11.
3. The Company pays the remaining amount to the Publisher in cash. Settlement and payment follow Article 10.
⑪ The payment gateway fee rates commonly applied to card payments are as follows.
| Region | Payment Gateway | Fee |
| - | - | - |
| Republic of Korea | Toss Payments | 3.2% |
| Overseas | Stripe | 5.5% |
⑫ The fee rates in paragraph 11 also apply to campaigns reserved through Ad.Note that are paid by card (see Article 10(4)).
⑬ Campaigns directly reserved by Publishers are not subject to the Ad.Note commission (Article 8(7)). Payment gateway fees are deducted only for card payment. Ad.Control fees (Article 5) are incurred based on ad impressions regardless of reservation route or payment method.
***
## Article 4(Reserved Ad Impression Standards)
Impressions used as the basis for Ad.Control fees and ad revenue settlement are counted per this Article.
① Impression counting standards by ad type are as follows.
1. Image/text creatives are counted as impressions when the ad area is at least 50% visible on screen and remains so for at least 0.5 seconds.
2. Video creatives are counted as impressions when the ad area is at least 50% visible and video playback begins. Completion status or completion rate does not affect impression counting.
3. Rewarded ads use video creatives but are counted by the same standard as image/text. They are counted as impressions even if the End-User stops watching before receiving the reward.
② To prevent duplicate counting, the following apply.
1. Creatives received in a single ad request are counted as impressions only once. Even if they leave the screen and return, they are not re-counted.
2. When a new ad request receives a different creative, that creative is counted as a separate impression.
3. In popup ad carousels, when the End-User navigates to a different campaign, each campaign's displayed creative is counted as one impression.
③ The following impressions are not counted as Ad.Control impressions and are not subject to fees or revenue settlement.
1. Impressions filled by bidding backfill ads (see Article 9(5))
2. Cases that do not meet the impression counting standards in paragraph 1
④ Regarding reference values, the following apply.
1. Fee billing and revenue settlement are based on values recorded on the Company's ad server.
2. Discrepancies may arise from the Publisher's own tracking or third-party analytics tools due to differences in measurement methods.
3. Publishers may raise objections within 30 days of receiving invoices or settlement statements. The Company will reply with the verification result within 15 days of receiving the objection. If errors are found, they will be reflected in the next billing/settlement.
⑤ The Company may change impression measurement methods for improved accuracy or industry standard changes. Changes affecting billing amounts will be notified by email 30 days in advance.
***
## Article 5(Ad.Control Fees and Payment)
① The Free tier (Stream) follows these rules.
1. Up to 10,000 reserved ad impressions per month may be used free of charge.
2. The free limit is calculated by summing all ad types and creative types, including impressions from rewarded ads and in-house ads.
3. The free limit is deducted in order of impression occurrence. Impressions beyond the limit are billed at the rates in paragraph 3.
4. Impressions from bidding backfill ads are neither included in the free limit sum nor subject to paid billing.
5. If no payment method (e.g., credit card) is registered, service usage is automatically suspended upon exceeding the 10,000 free impressions. During suspension, reserved ad delivery is stopped. Normal usage resumes upon payment method registration.
② The Paid tier (Wave) follows these rules.
1. Billing begins from the moment the free limit (10,000 impressions/month) is exceeded.
2. Fees are calculated based on the actually displayed creative, with rates per the price table in paragraph 3. The rate depends on which creative is displayed even in the same Ad Unit. When exceptions are defined in the price table (e.g., rewarded ads), those exceptions apply.
③ Fee rates by creative type are as follows.
| Category | Fee per 1,000 impressions (VAT excluded) |
| - | - |
| Image/Text creative | KRW 280 |
| Video creative | KRW 560 |
| Rewarded ad (video creative only supported) | KRW 280 |
④ Application of fees follows these rules.
1. Rewarded ads use only video creatives, but are billed at KRW 280 per 1,000 impressions (same as image/text) instead of the video creative rate (KRW 560).
2. The rates in paragraph 3 apply only to reserved ads. Bidding backfill ads are not subject to Ad.Control fees; backfill ads are settled by the commission method per Article 9.
3. The application of fees/commissions by reservation route and ad type is as follows.
* Reserved ads directly booked by Publishers: Ad.Control fees (paragraph 3) apply, and no sales commission applies. Payment gateway fees (Article 3(11)) are deducted only when Advertisers pay by card.
* Campaigns reserved through Ad.Note: Ad.Control fees (paragraph 3) and 20% sales commission (Article 8(7)) apply. For card payments, payment gateway fees are additionally deducted.
* In-house ads: Ad.Control fees (paragraph 3) apply.
* Bidding backfill ads: Ad.Control fees do not apply; only the 20% operating commission (Article 9(5)) is deducted.
⑤ Payment procedure is as follows.
1. Invoice: The Company sends an invoice listing the previous month's usage and billing amount to the registered email. The invoice separately displays impressions and amounts by creative type.
2. Automatic Payment (default): On the 5th of every month, fees for the previous month's usage are automatically charged to the registered credit card.
3. Tax Invoice Then Manual Payment (upon agreement): By separate agreement with the Publisher, payment may be changed to tax invoice issuance followed by payment. In this case, billing and payment deadlines follow the same standards as the settlement schedule in Article 10(5).
| Step | Timing | Details |
| - | - | - |
| 1. Notification of Billing | By the 5th of the month following the usage month (M+1) | Previous month's usage and billing amount notified to the registered email |
| 2. Tax Invoice Issuance | By the 10th of the following month (M+1) | The Company issues a tax invoice. If no objection is raised within the deadline, the billing details are deemed approved. However, objections under Article 4(4)(3) remain available. |
| 3. Fee Payment | Within 30 days of tax invoice issuance date | Publisher deposits to the Company's designated account |
⑥ Handling of non-payment follows these rules.
1. If fees are not normally paid on the payment date, the Company notifies the Publisher of the non-payment and payment request by email.
2. The Company grants a grace period of at least 7 days after the notification. Service can be normally used during this period.
3. If payment is not made after the grace period, service may be suspended or the agreement may be terminated.
⑦ Late fees accrue starting the day after the grace period in paragraph 6 ends. Late fees are not charged if payment is completed within the grace period. The late fee rate is 12% per annum, calculated daily for the delay period, and added to the next month's invoice.
⑧ Billing entities by region are as follows.
1. Users in the Republic of Korea: Open Rhapsody Co., Ltd.
2. Users outside the Republic of Korea: Jumoney Inc., subsidiary of Open Rhapsody
***
## Article 6(Term, Termination, and Refund)
① The Service is renewed monthly and automatically renews for the next month unless a separate termination request is made before the usage period ends.
② Termination methods are as follows.
1. Publishers may request termination at any time by email ([contact@adrop.io](mailto:contact@adrop.io)).
2. Service may be used until the end of the month in which the termination request is received.
3. Existing data may be retained by the Company for the period required by relevant laws even after termination.
③ Fee settlement and refund upon termination follow these rules.
1. The Service is a usage-based post-paid model where the impressions actually occurring in a given month are billed the following month, not a prepaid period-based model. Since there is no prepaid fee, the concept of refund for the remaining period does not apply.
2. Even upon termination, fees for impressions occurring until termination are normally billed under Article 5 and must be paid by the Publisher.
3. In case of duplicate payment or overbilling, the Company will refund the difference or deduct it from the next invoice.
***
## Article 7(Service Level Agreement, SLA)
① Guaranteed scope and measurement standards are as follows.
1. Guaranteed Level: The Company guarantees a monthly uptime of 99.9% or higher for reserved ad delivery.
2. Incident States
* The Company's ad server fails to respond to ad requests
* The server responds with errors
* Reserved ads are not delivered due to Company system issues despite having ads to deliver
3. Incident Duration Calculation
* Incident duration is calculated from the time the Publisher's report is received to the time the Company notifies the Publisher of recovery completion.
* If the Company detects the incident first via its own monitoring, calculation begins from the time of Company detection.
* The recovery completion time is when the Company confirms normal ad delivery has resumed and notifies the Publisher.
* Incident duration is measured in minutes, and the total incident duration for the month is used to calculate uptime.
* Periods not reported and not detected by the Company are not included in incident duration. However, if the incident start time is subsequently confirmed by objective materials such as Company server logs and monitoring records, incident duration is calculated from that time.
4. Cases Not Considered Incidents
* Ads not displayed due to no reserved ads to deliver
* Cases where backfill ads are not filled
* Cases due to Publisher setting errors/app errors or End-User device/network environment
5. Basis of Measurement
* Start/end times of incidents are based on the Company's report reception records, monitoring records, and recovery notification records.
* Scheduled maintenance times previously notified are excluded from both total time and incident duration.
② Incident compensation standards are as follows.
1. Base Amount for Credit Calculation
* Credits are calculated based on the monthly average Ad.Control fees for the 3 months preceding the incident month (excluding VAT).
* If the usage period is less than 3 months, the monthly average of the actual usage period is used. If there is no prior usage record, the Ad.Control fees of the incident month are used.
* Ad.Note revenue, backfill revenue, and Advertiser ad fees are not subject to calculation.
* Publishers using only the Free tier (Stream) with no fees incurred are not eligible for credits, as there are no fees to deduct.
2. Compensation Amount
* Compensation is provided as service fee credits (not cash) and is first deducted from Ad.Control fees incurred after approval.
* Credits not fully deducted in one billing cycle do not expire and are carried over to subsequent invoices.
* Availability drop is defined as 99.9% minus the actual uptime for the month (e.g., actual uptime 99.5% → drop 0.4%).
| Availability Drop | Credit |
| - | - |
| 0.1% or more \~ Less than 3% | 10% of base amount |
| 3% or more \~ Less than 5% | 20% of base amount |
| 5% or more | 30% of base amount |
③ Compensation claim procedure and limits are as follows.
1. Step 1 — Incident Report
* Upon discovering issues such as ads not being delivered, Publishers must promptly report to the technical support email ([tech-support@adrop.io](mailto:tech-support@adrop.io)), including the time of occurrence, symptoms, and affected Ad Units.
* Incident duration is calculated from the report reception time, so delayed reports reduce the compensable duration. However, if the Company detected the incident first via monitoring, calculation begins from the Company's detection time (see paragraph 1(3)).
* Upon receiving reports, the Company responds per paragraph 4 and, if confirmed as an incident, notifies the Publisher of the fact and scope of impact.
2. Step 2 — Compensation Claim
* Uptime is confirmed monthly, so compensation claims can be made after the incident month ends.
* Publishers must claim compensation by email ([contact@adrop.io](mailto:contact@adrop.io)) by the end of the month following the incident month. Including the Step 1 report details (reception time or reply email) will expedite verification.
* The Company will review report reception and recovery notification records within 15 days of the claim and notify the approval status and credit amount by email.
* Approved credits are deducted in order from Ad.Control fee invoices issued after notification. Remaining amounts are carried over until fully deducted.
* No credits are provided for claims not submitted within the deadline.
3. Compensation Limits
* Credits provided for incidents in one month do not exceed 30% of the base amount.
* Credits are not refundable in cash and cannot be used for purposes other than fee deduction or transferred to third parties.
* Upon contract termination, unremoved credits expire.
* The Publisher's remedy for the Company's failure to comply with SLA is limited to credits under this Article. However, in case of willful misconduct or gross negligence by the Company, Article 17 applies.
④ Response and resolution times follow these rules.
1. Reporting Channels
* Incident reports are received 24 hours a day via the technical support email ([tech-support@adrop.io](mailto:tech-support@adrop.io)).
* In preparation for emergency situations where ad delivery is entirely stopped, the Company provides Publishers with emergency contact channels (contact persons or dedicated channels) at the time of contract.
* When the Company detects an incident first via monitoring, it immediately notifies affected Publishers.
2. Response and Resolution Times
* The Company aims to resolve issues within 48 hours. However, resolution may take longer depending on the nature of the issue. In such cases, the Company will provide the reason for the delay and expected schedule, and share progress regularly.
* The response and resolution times in this section are the Company's target standards. When causes lie in external factors or reproduction is difficult, times may vary. The Publisher's remedy for failure to comply is limited to credits under paragraph 2.
3. Emergency Incident Recovery Procedure
* Reception/Confirmation: Recognize the incident via report or monitoring and determine the scope of impact.
* Temporary Measures: Roll back to the previous version, switch to a bypass route, or take other measures to prioritize resuming ad delivery.
* Recovery: Eliminate the root cause and restore normal state.
* Normal Confirmation: Verify that ad delivery and metrics have returned to normal, then notify the Publisher of recovery completion.
* Post-Incident Report: Provide a report on the cause, actions taken, and preventive measures to the Publisher within 5 business days of recovery completion.
⑤ The following cases are excluded from incident duration counting and credit compensation.
1. Suspensions due to previously notified scheduled maintenance and emergency security measures
2. Force majeure such as earthquakes, typhoons, floods, wars, and epidemics
3. Internet network failures by telecommunications carriers, or widespread outages by cloud providers
4. Publisher's incorrect settings or operational inexperience
5. Publisher's own app/website errors or Publisher server issues
6. End-User's device issues or network environment
7. Policy changes or outages by external platforms such as Google and Apple
8. External ad network (backfill) outages or ad shortages
9. Cases where ads are not displayed due to no reserved ads to deliver (no-fill)
10. Issues arising from continued use of SDK versions whose support has ended by the Company (see Article 13(4))
11. Issues arising during the use of beta/pilot features (see Article 13(5))
12. Periods when the Company suspended the Service due to the Publisher's reasons such as non-payment or prohibited conduct (see Article 12(4) and Article 15(3))
13. Suspensions due to work performed at the Publisher's request
***
## Article 8(Ad.Note Service)
① Ad.Note is a supplementary service of Ad.Control that promotes Publisher ad products to Advertisers and enables Advertisers to directly reserve and purchase them. It consists of the following two channels.
| Channel | Address | Content Displayed |
| - | - | - |
| Ad.Note Advertising Platform | [adnote.adrop.io](http://adnote.adrop.io) | Publisher ad products are listed. Advertisers browse products and reserve/purchase campaigns here. |
| Ad.Note Community | [adrop.io/adnote](http://adrop.io/adnote) | Product brochures and advertising benefit information registered by Publishers in the Ad.Control Console are displayed. |
② Ad.Note usage follows these rules.
1. Usage is chosen by the Publisher. Ad.Note is not automatically used; ad products are listed only when the Publisher applies for listing per paragraph 3. Failure to apply does not affect Ad.Control's reserved ad delivery.
2. Advertisers can browse ad products listed on the Ad.Note advertising platform and reserve/purchase campaigns per the pricing model and targeting conditions defined in Article 3.
3. Ad.Note is available to Publishers using the Wave tier.
4. Revenue generated through Ad.Note is subject to the sales commission in paragraph 7, and Ad.Control fees are separately incurred for ad impressions per paragraph 8. Settlement and payment of revenue follow Article 10.
③ Ad product listing procedure and display conditions are as follows.
1. Listing is not automatic. Publishers must directly apply for listing in the Ad.Control Console.
2. The Company reviews submitted ad products. Products that pass review begin display on the Ad.Note advertising platform ([adnote.adrop.io](http://adnote.adrop.io)).
3. Even after listing is complete, only products set as public by the Publisher are displayed on the Ad.Note advertising platform. Changing a product to private stops its display on the platform.
4. On the Ad.Note community, product brochures and advertising benefit information registered by Publishers in the console are displayed. Publishers must manage registered materials to ensure they are factually accurate. To modify or take down materials, Publishers change the registration content in the console.
5. The Company may reject listings or halt display when ad products or registered materials do not comply with relevant laws, Company policies, or Advertiser protection standards, and will inform the Publisher of the reason.
④ Campaigns reserved through Ad.Note are distinguished in the Ad.Control Console as follows, allowing Publishers to verify Ad.Note reservations at any time.
1. In the Reservation Management menu, a blue Ad.Note logo is displayed next to the campaign name.
2. The contract manager of the campaign is displayed as 'Ad.Note'.
⑤ Campaign approval authority rests with Publishers per these rules.
1. Even when Advertisers request reservations through Ad.Note, campaigns are not immediately delivered. Publishers directly decide whether to approve campaigns.
2. Publishers may review campaign details (Advertiser, period, placement, creatives, amount, etc.) in the Reservation Management menu of the Ad.Control Console and approve or reject them.
3. Publishers may reject campaigns that do not match their service characteristics or ad policies, or that include inappropriate creatives. The Company does not intervene in Publisher approval decisions.
4. Only approved campaigns are delivered. Unapproved campaigns are not delivered, and no sales commission or ad fee settlement occurs in such cases.
5. For Advertiser protection, Publishers must decide on reservation approval before the campaign start date. Publishers bear responsibility for damages to Advertisers caused by unjustified delays in approval decisions.
⑥ Other campaign operation matters are as follows.
1. Payment, cancellation, and penalty details for reserved ads are governed by the separate 「[Ad.Note Campaign Reservation Terms](/legal/adnote-campaign-terms)」, which takes precedence.
2. If Advertisers cancel or change campaigns, penalties defined in the relevant policy may be charged, potentially affecting Publisher revenue.
⑦ Sales commission follows these rules.
1. The Company deducts 20% of total ad revenue generated through Ad.Note as sales commission.
2. Scope of Application: Regardless of prior transaction history or direct sales relationships between the Publisher and Advertiser, a 20% sales commission applies to all campaigns reserved through the Ad.Note platform. All campaigns confirmed as Ad.Note reservations per paragraph 4 fall under this scope.
3. Publishers cannot request exemption or reduction of sales commission on the grounds that the Advertiser was an existing customer.
4. If a separate sales distribution agreement exists, the commission terms of that agreement take precedence.
⑧ Separate Ad.Control fees follow these rules.
1. When campaigns reserved through Ad.Note are delivered, Ad.Control fees (Article 5) are incurred separately from the 20% sales commission for the campaign's ad impressions.
2. Accordingly, Publishers are subject to both of the following.
* Receive settlement of revenue after deducting the 20% sales commission from ad revenue.
* Separately bear Ad.Control fees for impressions generated by the campaign.
3. Ad.Control fees are calculated by creative type rates per Article 5(3) and included in the monthly fee invoice.
4. Since revenue settlement and fee billing follow different schedules, fees are not deducted in advance from settlement amounts.
***
## Article 9(Bidding Backfill Ads)
① The overview and usage conditions of bidding backfill ads are as follows.
1. Backfill ads are a supplementary service of Ad.Control that automatically fills the Publisher's ad spaces with external ad network ads via real-time bidding when both reserved ads and in-house ads are unavailable.
2. Backfill ads are not enabled by default. Publishers must apply for 'Turn on Backfill Ads' at the Ad Unit level in the Ad.Control Console to activate them. Units without application will not display ads when no ads are available.
3. Even for activated units, backfill ads are delivered only after the Company connects external ad network placements to the unit.
4. When using automatic refresh of banner ads (replacing with new ads at set intervals), if the backfill ad response does not arrive within a set time (approximately 3 seconds) at the refresh point, the refresh is skipped and the existing ad is retained.
② Backfill ads depend on formats provided by external ad networks. Not all ad types are supported in all environments. Supported ranges by environment and ad type are as follows.
| Ad Type | Android · iOS · Flutter · React Native | Web |
| - | - | - |
| Banner | Supported | Limited support (display ad format) |
| Interstitial | Supported | Not supported |
| Rewarded | Supported | Not supported |
| Native | Supported | Not supported |
| Popup | Not supported | Not supported |
| Splash | Not supported | Not supported |
③ Environment-specific notes are as follows.
1. Web Environment
* When native ad spaces on websites have no reserved ads, backfill ads are not displayed in native form (blending with content). They may be displayed as standard display ads in the Publisher's designated area, or not displayed.
* Web backfill ads can be selected to either automatically fit ad area size (responsive) or fit a Publisher-specified height (fixed).
2. App Environment
* Popup ads and splash ads do not support backfill in any environment. Without reserved ads, these ads are not displayed.
④ Characteristics of bidding ads follow these rules.
1. Due to the real-time auction structure of bidding backfill ads, pre-reviewing ad content before display may be technically impossible.
2. The Company provides filtering features to screen inappropriate ads but cannot guarantee complete blocking in all cases.
3. Publishers who wish to block specific types of ads may use the blocking settings provided by the Company.
4. Backfill ad fill rates, unit prices, and revenue levels vary depending on external ad network market conditions. The Company does not guarantee specific levels.
⑤ Backfill ad commission follows these rules.
1. The Company deducts a 20% operating commission from backfill ad revenue and pays the remaining amount to the Publisher in cash (payment = backfill ad revenue × 80%).
2. Revenue used as the settlement basis is the amount finalized and paid by the external ad network for that month's backfill ad performance. If the external ad network deducts revenue due to invalid traffic, the reduced amount serves as the basis.
3. Backfill ad impressions are not subject to Article 5 fees. Ad.Control fees are not charged; the only cost borne by the Publisher is the 20% operating commission in this paragraph.
4. Revenue settlement, payment, and exchange rate application follow Article 10.
***
## Article 10(Revenue Settlement and Payment)
This Article defines the settlement procedure for revenue paid by the Company to Publishers. The billing and payment of Ad.Control fees paid by Publishers to the Company follow Article 5.
① The scope of application of this Article is as follows.
| Settlement Target | Basis |
| - | - |
| Ad revenue from campaigns reserved through Ad.Note | Article 8 |
| Card payment portion of campaigns directly booked by Publishers (Company handles settlement) | Article 3(10) |
| Bidding backfill ad revenue | Article 9 |
② For campaigns directly booked by Publishers and paid by bank transfer, ad fees do not pass through the Company and are not subject to settlement under this Article.
③ The reference month (M) in the settlement schedule varies by settlement target as follows. Even if a campaign spans multiple months, settlement is processed at once based on the month the campaign ended.
| Settlement Target | Reference Month (M) |
| - | - |
| Ad.Note campaign revenue | Month in which the campaign ended |
| Card payment settlement handling | Month in which the campaign ended |
| Backfill ad revenue | Month in which ad impressions occurred |
④ Payment amount is calculated by deducting the following items from ad revenue.
| Deduction Item | Scope | Details |
| - | - | - |
| 20% sales commission | Ad.Note campaigns | Article 8(7) |
| 20% operating commission | Backfill ads | Article 9(5) |
| Payment gateway fees | All card-paid campaigns | Rates per Article 3(11) (Toss Payments 3.2% in Korea, Stripe 5.5% overseas) |
| Taxes and public charges | All | See item 2 |
1. When campaigns reserved through Ad.Note are paid by card, both the 20% sales commission and payment gateway fees are deducted, and the remaining amount is paid to the Publisher.
2. Taxes and Public Charges: If withholding tax or other public charges arise under the laws of the Republic of Korea or the Publisher's country of residence, the Company deducts the statutory amount before payment. Publishers eligible for exemption/reduction must submit relevant supporting documents in advance.
3. Uncollected Amounts: Amounts not actually collected by the Company due to payment cancellation, refund, or card approval reversal are excluded from settlement, or deducted from the next settlement if already paid.
⑤ Settlement and payment schedule is as follows.
| Step | Timing | Details |
| - | - | - |
| 1. Settlement Statement Notification | By the 5th of the month following the reference month (M+1) | Settlement statement listing ad revenue, deducted commissions, and net payment amount is sent to the registered email |
| 2. Tax Invoice Issuance | By the 10th of the following month (M+1) | The Publisher issues a tax invoice. If no objection is raised within the deadline, the settlement statement is deemed approved. However, objections under Article 4(4)(3) remain available. |
| 3. Revenue Payment | Within 30 days of tax invoice issuance date | Net payment amount paid in cash to the Publisher's registered account |
1. Example: For revenue from campaigns ending in March and backfill revenue occurring in March, settlement statements are sent by April 5. If the tax invoice is issued by April 10, payment is made within 30 days from that date.
2. For backfill ads, if there are delays in receiving confirmed revenue from external ad networks or other reasons beyond the Company's control, the Company will inform the Publisher of the reason and expected payment date, and pay without delay after confirmation.
⑥ Exchange rate application follows these rules.
1. Overseas ad revenue is generated in USD and converted to KRW for payment.
2. Exchange rates are based on the average KRW/USD standard exchange rate published by the Bank of Korea Economic Statistics System (ECOS) for the revenue generation month (M), with amounts under 1 KRW rounded up in KRW conversion.
⑦ Objections to figures and amounts in settlement statements follow Article 4(4).
⑧ The Company is not liable for payment delays or errors caused by external parties such as credit card companies and payment gateways (PG), and will cooperate sincerely in resolving issues.
***
## Article 11(Data and Privacy Protection)
① Data ownership follows these rules.
1. Data generated and managed through the Service (e.g., ad statistics, revenue information) belongs to the Publisher.
2. The Company does not sell or share such data with third parties without the Publisher's explicit consent. However, in case of court orders or legitimate legal requests, the Company may respond in accordance with relevant laws.
② Application of the privacy policy follows these rules.
1. All matters concerning personal information processing (items collected, purposes, retention periods, destruction methods, international transfer, End-User rights, etc.) are defined in the Company's 「[Privacy Policy](/legal/privacy)」, which takes precedence over these Terms.
2. The Privacy Policy is always posted on the Company's website. Changes are notified per the procedures defined in the policy.
③ Responsibility for obtaining End-User consent follows these rules.
1. Publishers bear sole responsibility for obtaining lawful consent (personal information collection/use, advertising identifier collection, personalized ad receipt, third-party provision, etc.) from End-Users of their app/web under relevant laws (Personal Information Protection Act, GDPR, CCPA, etc.).
2. If a dispute arises from End-Users or third parties due to the Publisher's failure to fulfill the obligations in the preceding item, the Publisher must indemnify the Company at its own expense and responsibility and compensate all damages incurred by the Company.
④ The Company holds the status of Processor processing data under the Publisher's delegation. The Publisher bears legal responsibility as the Controller determining the purpose and means of data collection and use.
⑤ Publishers must not transmit the following information to the Company via the SDK's user identifier (setUid), user properties, or custom events.
1. Sensitive information under Article 23 of the Personal Information Protection Act (information on health and medical treatment, sex life, thoughts/beliefs, political opinions, labor union/political party affiliation, genetic information, criminal records, etc.)
2. Unique identification information under Article 24 of the Personal Information Protection Act (resident registration number, passport number, driver's license number, foreign resident registration number)
3. Personal information of children under 14 without legal guardian consent
⑥ If the Company confirms that such information has been transmitted, it may block reception of the data or delete it, and will notify the Publisher. Damages arising from the transmission of such information are the responsibility of the Publisher.
***
## Article 12(Publisher Obligations)
① Publishers must properly integrate the Company-provided SDK into their app or website. Technical documentation and inquiry channels for integration are per Article 13(3). Responsibility for integration and console operation is as follows.
1. Verification of Integration Results: After integrating the SDK, Publishers must directly verify that ads are properly displayed in the intended positions. Publishers bear responsibility for consequences of incorrect integration (ads not displayed or displayed in wrong placements).
2. Console Operations: Settings and operations performed in the console under the Publisher's account (ad product/price/targeting settings, campaign creation/modification/start/stop, creative approval, payment method registration, etc.) are deemed the Publisher's intent.
3. Verification Before Saving: Before saving settings or starting campaigns, Publishers must verify that the content matches their intent. Consequences attributable to misunderstanding the interface are the Publisher's responsibility.
4. Impressions from Incorrect Settings: Even when ads are delivered due to Publisher setting errors, actual impressions are billed per Article 5.
5. Inquiries: If uncertain about console usage or setting results, Publishers may inquire with the Company before starting campaigns.
② Ad space settings follow these rules.
1. Publishers must accurately use the unique Unit ID issued by the Company for each ad space.
2. Publishers must not misappropriate others' Unit IDs or use test Unit IDs in actual services.
3. Responsibility for managing authentication information (account information, API keys, etc.) lies with the Publisher. If leakage is suspected, Publishers must immediately notify the Company.
③ Publishers must not engage in the following acts.
1. Displaying ads on pages containing content that violates relevant laws or Advertiser policies (e.g., adult content, gambling, hate speech)
2. Intentionally hiding or obscuring ads from End-Users
3. Disassembling, decompiling, or modifying the SDK without the Company's prior written consent
4. Reselling the Service to third parties or lending Service usage rights without the Company's prior written consent
5. Clicking ads oneself or requesting/urging others to click (click fraud)
6. Artificially generating impressions or clicks using automated programs or scripts
7. Deceiving End-Users into clicking ads
Items 5 through 7 of this paragraph apply only to placements where campaigns reserved through Ad.Note are delivered and to Ad Units with backfill ads activated. Items 1 through 4 apply to all Ad Units.
④ If a Publisher engages in the prohibited acts in the preceding paragraph, the Company may suspend the Service or terminate the agreement without prior notice. If damages are incurred by the Company, the Publisher must compensate.
***
## Article 13(Service Provision Method and Support Scope)
① The Service is a standard cloud service (SaaS) provided to all Publishers with the same features and manner. The Company provides the Service as-is (in the state it is currently provided) and does not provide feature development, interface modifications, dedicated server construction, or separate version distribution for individual Publishers.
② The scope of warranty is as follows.
1. The Company guarantees the monthly uptime (SLA) for reserved ad delivery specified in Article 7 but does not guarantee the following.
* That the Service meets a specific purpose or business plan of the Publisher
* That the Service is free from errors or interruptions
* Specific levels of ad revenue, ad prices, or fill rates
* Consistent operation on specific devices, OS versions, or browsers used by the Publisher
2. Ad revenue varies depending on ad market conditions, the Publisher's user base and characteristics, ad placement, etc.
③ The scope of technical support is as follows.
1. Technical Inquiries: Technical inquiries such as SDK integration and ad delivery errors are received at [**tech-support@adrop.io**](mailto:tech-support@adrop.io).
2. Other Inquiries: Non-technical inquiries such as contracts, fees, settlement, and termination are received at [**contact@adrop.io**](mailto:contact@adrop.io).
3. Technical documentation is available on the Company's website ([help.adrop.io](http://help.adrop.io)).
4. Support scope is limited to issues with the Company-provided SDK and console themselves. Publisher app/website development, debugging, server operation, and design work are not included in the support scope.
④ SDK version support policy follows these rules.
1. Publishers must strive to keep the Company-provided SDK up to date.
2. The Company may end support for specific SDK versions due to security, external platform policy changes, technical support termination, etc. In such cases, notice is provided by email 30 days in advance.
3. The Company is not liable for errors, ad non-delivery, or revenue loss arising from continued use of unsupported versions. Such cases are excluded from SLA compensation.
⑤ Features labeled as 'beta', 'pilot', or 'preview' are not formal services and may be changed or discontinued without notice. SLA and compensation rules do not apply to beta features.
⑥ Third-party services follow these rules.
1. Bidding backfill ads are provided through third-party ad networks.
2. Advertiser payments are processed through payment gateways such as Toss Payments and Stripe.
3. Publishers must also comply with the policies and terms of such third parties. The Company is not liable for account suspension, ad cessation, or revenue non-payment due to third-party policy violations.
⑦ The Company routinely validates Publisher ad requests for service stability and invalid traffic prevention, and may not deliver ads to requests that fail validation. This follows these rules.
1. The rules and detailed standards used in validation (thresholds, criteria, request frequency limits, etc.) are not disclosed, as disclosure would enable circumvention of those standards.
2. Requests not delivered are not counted as impressions, and no Ad.Control fees are incurred for them.
3. Ad non-delivery due to measures under this paragraph is not considered an incident under Article 7. The Company is not liable for Publisher revenue reductions arising therefrom.
***
## Article 14(Intellectual Property Rights)
① Company rights follow these rules.
1. All intellectual property rights to all software, code, SDK, documents, designs, databases, and other assets related to the Service belong to the Company.
2. Publisher use of the Service does not transfer ownership or intellectual property rights of the preceding assets to the Publisher. The Publisher only holds the usage rights defined in paragraph 4.
② Publisher content follows these rules.
1. Rights and responsibility for content provided by Publishers through their app/website belong entirely to the Publisher.
2. The Company does not use Publisher content for purposes other than ad delivery.
③ Data usage permission follows these rules.
1. Publishers permit the Company to analyze and process service usage data for service provision, service improvement, statistics generation, and development of new services.
2. Even in this case, the Company does not use personally identifiable information and processes only anonymized information or statistical form under the Personal Information Protection Act. When processing pseudonymized information, the Company complies with the procedures and safety measures defined in Articles 28-2 and 28-4 of the same Act.
④ SDK usage rights follow these rules.
1. The Company grants Publishers the right to use the SDK only for the purpose of displaying ads on Publisher-operated apps/websites during the contract period.
2. This right is non-exclusive and may not be transferred to third parties or sublicensed (resold, leased, etc.).
3. This right merely enables SDK use and does not transfer ownership or intellectual property rights of the SDK.
4. Publishers may not use the SDK in ways prohibited in Article 12(3) (disassembly, decompilation, modification, third-party resale, etc.).
5. Upon contract termination, this right expires simultaneously. Publishers must immediately cease SDK use and remove the SDK from subsequently distributed app/website versions.
***
## Article 15(Service Changes and Suspensions)
① Service content changes follow these rules.
1. The Company may change all or part of the Service as needed for operations or technology.
2. Important changes unfavorable to Publishers (e.g., fee increases, discontinuation of major features, changes to impression measurement methods) will be notified by email 30 days in advance, including content and effective date.
3. Urgent changes (e.g., security vulnerability responses) may be made without prior notice, in which case ex-post notification is provided.
② The Company may temporarily suspend all or part of the Service in the following cases. Suspensions are notified by email in advance; if prior notification is impossible, ex-post notification is provided.
1. Unavoidable cases such as scheduled system maintenance, equipment replacement, or malfunctions
2. When key telecommunications carriers under the Telecommunications Business Act suspend telecommunications services
3. Force majeure such as natural disasters
③ The Company may entirely suspend a Publisher's ad delivery in the following cases. Revenue losses during this period are not subject to SLA compensation.
1. Non-payment: Fees not paid after the grace period in Article 5(6) has passed
2. Legal/Policy Violation: Content violating relevant laws or Advertiser policies on the Publisher's app/website
3. Security Incident: Publisher account or authentication information leaked, causing or potentially causing unauthorized delivery
4. External Demand: Orders from relevant authorities or demands from external platforms such as Google/Apple
5. Advertiser Protection: Serious harm to Advertisers has occurred or is clearly imminent
④ When suspending under the preceding paragraph, the Company notifies the reason and lifting conditions together. However, in cases requiring immediate action such as paragraphs 3(3), (4), and (5), the Company may suspend first and notify without delay. When the Publisher resolves the cause and notifies the Company, the Company will confirm and resume delivery without delay.
***
## Article 16(Amendments to the Terms)
① The Company may amend these Terms. Upon amendment, the content and effective date are notified by email 30 days in advance.
② If Publishers disagree with the amended Terms, they may terminate the agreement before the effective date.
③ If no termination is expressed by the effective date, or if the Service is continuously used after the effective date, the amended Terms are deemed accepted.
***
## Article 17(Limitation of Liability)
① The Company is liable for direct damages incurred by Publishers due to failure to comply with the specified service level (SLA) or technical defects. The Company's liability shall not exceed the fees for the immediately preceding one month from the date of damage occurrence.
② The limitation of liability in paragraph 1 does not apply to damages caused by the Company's willful misconduct or gross negligence. The Company bears liability under relevant laws in such cases.
③ The Company is not liable for damages caused by the following.
1. Publisher negligence (SDK integration errors, SDK misuse, use of unsupported versions, console setting errors, etc.)
2. Fault of third parties such as Advertisers, ad networks, payment gateways, and telecommunications carriers
3. Force majeure such as earthquakes, typhoons, floods, wars, and epidemics
4. Issues due to End-User device, network, or browser environments
5. Indirect/consequential damages such as ad revenue reductions or unmet expected revenue
④ Disputes arising during campaign operation follow these rules.
1. If disputes or damages arise between Publisher and Advertiser due to the Publisher's incorrect SDK integration or console operation, the Publisher bears responsibility. The following cases apply.
* Setting campaign period, budget, unit price, or targeting differently from intent
* Accidentally starting or stopping campaigns, or registering/approving incorrect creatives
* Incorrectly connecting Ad Units or removing them from placements, resulting in ads not being delivered per contract
* Misunderstanding console screens or features
2. The Company sincerely cooperates in cause identification and provision of delivery/performance data for such disputes but does not bear responsibility for compensation to Advertisers or re-delivery.
3. The preceding items are distinguished from issues caused by technical defects in the Company's system. If Company system defects are confirmed, Article 7 and paragraph 1 apply.
***
## Article 18(Dispute Resolution)
① The interpretation and application of these Terms are governed by the laws of the Republic of Korea.
② In case of disputes, both parties will endeavor to resolve them amicably through sincere consultation for at least 30 days before litigation.
③ Litigation related to these Terms shall be under the exclusive jurisdiction of the Seoul Central District Court.
***
## Article 19(Miscellaneous)
① These Terms constitute the entire agreement between the parties regarding the use of the Service and supersede all prior oral or written agreements.
② These Terms are written in Korean. In case of discrepancies in meaning between the Korean version and translations, the Korean version prevails.
③ Assignment of rights follows these rules.
1. Publishers may not assign rights or obligations under this agreement to third parties without the Company's prior written consent.
2. The Company may notify Publishers and assign rights when rights are transferred due to merger, division, or business transfer.
④ Even if some provisions of these Terms are deemed invalid under relevant laws, the remaining provisions remain effective. Invalid provisions are replaced with valid content closest to their purpose.
⑤ Notification methods follow these rules.
1. Company notifications are sent to the email address registered by the Publisher in the console. They are deemed delivered 3 business days after sending. However, if the Publisher actually receives them earlier, they are deemed delivered upon actual receipt.
2. If the email address changes, Publishers must immediately update it in the console. Publishers bear any disadvantages arising from neglecting this.
***
## Addendum
Article 1(Effective Date) These Terms take effect on October 1, 2026.
Article 2(Related Documents) Documents applied together with these Terms are as follows.
1. 「[Privacy Policy](/legal/privacy)」
2. 「[Ad.Note Campaign Reservation Terms](/legal/adnote-campaign-terms)」
# Ad.Control
Source: https://docs.adrop.io/release-notes/ad-control
Adrop Console and Platform Feature Updates
## Check video viewing-quality metrics across every report level
Valid Views / Valid View Rate and Completed Views / View-Through Rate (VTR) columns are now available across unit, product, campaign, and creative reports and detail screens, so you can quantitatively compare video creative performance and per-publisher efficiency.
**What's new**
* Valid Views count plays of 3 seconds or longer; Completed Views count plays through to the end
* The same metrics are surfaced to advertisers in their Ad Platform, so you can show the viewing value of your video inventory directly
* Only counted for items where video creatives were served
**Where to find it**
* Console > Report
* Console > Ad Unit detail > Performance
* Console > Ad Product detail > Performance
* Console > Campaign detail > Performance / Creatives table
## Performance Report opens directly in a new window with campaign and product filters applied
Clicking the **Performance Report** button on a product or campaign detail opens the Report menu in a new window with that item's filter pre-applied, so you can begin analysis with full date ranges, segments, and detailed filters from the moment you arrive.
**What's new**
* Works the same way in both the Console and the Ad Platform
* Keep the campaign detail open and compare the report side-by-side in the new window
* All Report menu features — CSV export, column editing, and more — are available immediately
**Where to find it**
* Publisher: Console > Booking Management > Campaign detail > Performance Report button
* Publisher: Console > Ad Products > Ad Product detail > Performance Report button
* Advertiser: Ad Platform > Campaigns > Campaign detail > Performance Report button
## Run Video Ads on All Sizes of Banner, Popup, and Native
Each unit now has an 'Accept Video Creatives' option, so you can receive video creatives on banner, popup, and native units — including custom sizes. Products that include any unit supporting video are automatically set as video-enabled products, letting you run image and video ads together in a single product without a separate Add-on setup.
**How to use**
* Turn on 'Accept Video Creatives' when creating a unit to receive video creatives for that unit
* Toggle the video controller on/off. When on, sound can be toggled; when off, it plays muted with auto-loop
* Advertisers can choose to register either image or video when uploading creatives
**Where to find**
* Publishers: Console > Create Ad Unit > Video Options > Accept Video Creatives / Video Controller / Video Skip Delay
* Advertisers: Ad Account > Campaign Detail > Add Creative > Select image or video
**Notes**
* Video Skip Delay setting has moved from the product to the unit
* CPM Video Add-on is no longer used. If at least one unit supporting video is included, the product is automatically created as a video-enabled product
* Billing criteria (based on the asset type actually served) remain unchanged
## Compare Backfill Performance at a Glance with Per-Unit Summaries
Units have been added to the Backfill report segments, and a 'By Date' checkbox lets you choose whether to expand by date. Unchecking it shows only the per-unit summary for a cleaner view.
**What's improved**
* Select 'Unit' in the Backfill report segment
* Uncheck 'By Date' to show only the per-unit summary
* When no segment is selected, date is the default axis, so the checkbox is disabled
**Where to find**
* Console > Reports > Backfill tab > Select segment
## Use the Console in Japanese Right Away
Japanese has been added to Adrop Landing, Ad.Control, and Ad.Note, so Japanese publishers can run ads in their own language right away. Korean publishers can also share the Japanese console screen directly with Japanese advertisers for sales.
**What's improved**
* Japanese can now be selected on Adrop Landing, Ad.Control, and Ad.Note
* Japanese publishers can operate in their own language without switching
* Korean publishers can directly share the Japanese console screen with Japanese advertisers
**Where to find**
* Landing page: Top language dropdown
* Console/Ad Account: Top-right My Account > Language > Select from dropdown
## Query Report Data Directly via API with API Keys
The Report API has been added, allowing you to query campaign performance and backfill revenue data via API without accessing the console. API keys can be generated and managed from the console management menu.
**Get started**
* Generate an API key at Console > Management > Integrations
* Query campaign, product, unit, and creative performance data with `report:read` permission
* Backfill ad revenue data is also available by unit and date
**Access**
* API Key Management: Console > Management > Integrations
* API Guide: Help Center [Integrations](/integrations/overview) tab
## Measure Ad Conversion Performance with Event-Based Audience Targeting
Conversion events such as purchase, add to cart, and inquiry completion have been added to audience targeting conditions, enabling you to build precise target groups by combining properties and events.
**How to use**
* Select events from +Filter when creating audience targeting to add target conditions
* Combine events and properties to target only users who performed specific actions
**Where to find**
* View events: Console > Events
* Use events in targeting: Console > Create Audience Targeting > +Filter in condition settings > Select Event
## Batch Creation for Units and Targeting
Create multiple units and targeting at once using the batch creation feature.
**What's improved**
1. **Bulk setup is now easier**
Upload an Excel file once to create multiple units and targeting simultaneously. Tasks that require multiple settings can now be handled much more efficiently.
2. **Manage settings systematically**
Organize and review conditions in Excel before uploading. Easier internal sharing and pre-verification help reduce configuration errors and maintain management consistency.
***
**Where to use**
* **Batch Unit Creation**: Click the 'Batch Create Units' button at the top right of the \[Ad Units] menu in the console
* **Batch Targeting Creation**: Click the 'Batch Create Targeting' button at the top right of the \[Audience / Contextual Targeting] menu in the console
## Precise Targeting Sales with Inclusion Period Setting
You can now set audiences to include users only for a specific period after they meet the targeting condition. If the condition is met again within the set period, the inclusion period is automatically renewed.
**What's improved**
1. **Only currently valid targets are automatically maintained**
Previously, users who met a condition once could remain in the audience even after time passed. Now, only users who have recently met the condition are kept as targets, reducing meaningless targeting.
2. **You can explain targeting criteria more clearly to advertisers**
With the inclusion period set, you can describe targeting criteria like 'targeting only users who met the condition within the last ○ days.'
***
**Settings Path**
You can configure this in \[Console] Targeting > 'Inclusion Period' on the Audience Targeting creation and edit page.
***
**Notes**
A default value is applied to the inclusion period, so there are no required settings or immediate actions needed.
## Set Advertiser Permissions in Context
The 'Advertiser Allowlist' has been removed. Each permission is now managed in the settings screen where it's actually used.
All advertiser permissions are accessible from \[Management] > \[Operations] in the console.
* **Private Product Access**: Set in Private Product Access settings
* **Manual Payment Permission**: Set in Payment Method Management > Manual Payment allowlist
* **CPP Budget Edit & Targeting Permission**: Set in Platform Policy > CPP Budget Edit & Targeting allowlist
***
## Improved Unit Selection for Product Creation
Finding and selecting units is now easier.
* Use filters and search to quickly find the units you need
* Unit list now displays more relevant information for distinguishing units
* Click the more button on the right to view unit details instantly
***
## Unit Set Renamed to 'Creative Set'
Unit Set has been renamed to 'Creative Set' to better reflect its purpose. Functionality remains the same.
* **Same format/size with same creative**: Use one creative set to display a single creative across multiple units
* **Different creatives needed for placement characteristics or fatigue**: Separate creative sets to register different creatives for each
**Terminology Changes**
* Unit Set → Creative Set
* Add Unit Set → Split Creative Set
You can instantly check the required number of creatives advertisers must upload based on the creative set configuration on the left side.
## Ad.Note AI Recommends Ad.Note Publishers to Advertisers
AI publisher recommendation feature has been added to the Ad.Note community. When advertisers enter their campaign goals, the most suitable products among Ad.Note publishers are recommended.
Through recommendations, you can naturally connect with more advertisers and create new revenue opportunities.
**How to Join Ad.Note**: Click 'Join Ad.Note' at the bottom left of the console, complete the checklist, and click the Join button.
**Publisher Profile Registration Required**: Register a PDF profile for advertisers in \[Management] → \[Ad Profile] → \[Ad Product Introduction]. The AI agent will analyze this and automatically recommend your products to advertisers.
For detailed guides on joining, please check the Help Center.
***
## Revenue Settlement View
You can check settlement statements for campaigns and Backfill ads in \[Management] → \[Revenue Payment].
***
## Video Usage Tracking
Video usage and fees are displayed separately in the usage card of \[Management] → \[Subscription & Billing].
## Targeting Menu Redesign
* **Menu Separation**: Targeting menu is now separated by Audience and Context. You can change the display order by dragging.
* **Terminology Changes**
* 'Targeting Category' → 'Targeting Option'
* 'Category Group' → 'Bundle Options'
* **Immediate Availability**: Targeting options are ready to use immediately upon creation, without requiring a separate status change.
* **Real-time Updates**: Targeting changes are immediately reflected in active campaigns and ad products. Create new targeting if you don't want to affect ongoing campaigns.
***
## Ad Product Targeting Settings Redesign
* **Location Change**: Targeting settings moved from Step 1 to Step 3 of ad product creation.
* **Product-specific Premiums**: Different premium rates can be applied per ad product, even for the same targeting.
* **Estimated Target Size**: Preview estimated target size when creating ad products and reserving campaigns.
* **Sub-option Details**: Console can now view detailed sub-options linked to the targeting options selected by advertisers.
***
## Video Ads Full Launch Notice (Q2 2026)
Starting April, video ads will transition to full operation with the following pricing:
| Ad Type | Pricing |
| - | - |
| Image Ads | Free up to 10K impressions/month Additional: 0.19 USD per 1,000 impressions |
| Video Ads | 0.38 USD per 1,000 impressions |
## Easily Select High-Performing Unit Sizes
No need to worry about which size is best for ad performance when creating units. You can now instantly view recommended unit sizes optimized for ad performance on PC/Mobile platforms with helpful guides.
**How to Configure**
* Check recommended sizes for each unit in \[Unit Creation/Edit] → \[Ad Size]
***
## Configure Native Unit Options More Intuitively
We've clearly separated required fields and additional fields when configuring native units. Now you can easily turn on/off key fields needed for native units in one place.
**Native Ad Fields**
* Advertiser Logo
* Advertiser Name
* Call-to-Action (CTA) Button
* Headline
* Body
**Native Additional Fields**
* You can more clearly recognize and configure the required selection option status for additional fields
***
## Support for All Countries and Timezones
You can now select all countries and timezones in the Console/Ad Platform, making global support much easier and enabling ad operations optimized for each region.
## End of Support for In-House Priority Display with Regular Campaigns
The 'Priority Display with Regular Campaigns' feature for in-house pricing products will be discontinued. The default delivery logic for in-house ads remains unchanged—they will only display when no CPP/CPM/CPC campaigns are running. The impression limit setting is still available.
## Ad.Note Community Launch
The Ad.Note community has officially launched. Browse advertising products and benefits, ask questions anonymously, and receive expert consultations. Publishers' products are automatically displayed in the community when they join.
***
## Join Ad.Note Directly from the Console
Complete Ad.Note registration directly from the console. Enter billing info and service details in \[Management], then click 'Join Ad.Note' to apply. After review, your products will be displayed in the community.
***
## Post Benefits to Ad.Note and Create More Opportunities
Create benefits in \[Management] → \[Benefits] to automatically publish them to the Ad.Note community. Benefits appear in the real-time popular section, allowing advertisers to request consultations immediately.
## Backfill Ads Feature Official Launch
Backfill ads are now automatically displayed during times when direct ads are not available, allowing you to capture revenue that was previously missed. Supported for banner and native ads, you can enable Backfill ads from the \[Ad Units] menu.
**Console (Publishers)**
* \[Ad Units] → Click the 'Enable Backfill Ads' button in the unit list to activate
* Select Web SDK integration status in the confirmation dialog, then click 'Enable Backfill Ads'
* After requesting, the unit status changes to 'Processing', and once connected, Backfill ads will automatically serve when no direct ads are available
**Ad Account (Advertisers)**
* This feature only affects publisher inventory management; advertisers' direct ad execution methods remain unchanged
**Notes**
* Native units must have the headline option enabled in image or ad text items for ads to display properly
* First-time Backfill ad activation requires an initial integration process (Adrop will contact you separately)
* Backfill ad revenue is settled in the month after next
* Android/iOS rewarded/interstitial units and Web native units will be available later
## Dark Mode Creative Support Feature Launch
Ad units now support dark mode creatives. You can run ads optimized for both light mode and dark mode environments with a single unit.
**Console**
* Dark mode creative receiving can be enabled for banner, popup, splash, and native ad units
* When turning on the dark mode option during unit creation/editing, advertisers must upload both light/dark mode creatives
* From SDK 1.6.0, dark mode can be selected when requesting ads
**Ad Account**
* When registering creatives for dark mode-enabled units, both light/dark mode images must be uploaded
* Registered creatives are automatically displayed according to the publisher app's dark mode environment
**Notes**
* Video does not support dark mode creative feature
* Splash dark mode preview background color setting removed
***
## Payment and Billing Information Improvements
We've enhanced the payment system to support international advertisers and global business expansion.
**Billing Information Required**
* Billing information (including billing address) is required when creating campaigns and changing plans
* Billing country follows the country setting in \[Console/Ad Account] → \[Ad Profile]
* Edit billing information: Ad Account in \[Management] → \[Payment & Billing], Console in \[Management] → \[Revenue Payment Info]
**My Payment Profile Feature Added**
* Save billing information and payment methods to a payment profile for easy import when creating new consoles or ad accounts
* Access from Console/Ad Account top-right profile → My Payment Profile menu
* Admin: Can register/edit billing info and payment methods / Viewer: Can use registered payment profiles
**Terminology Changes**
* 'Tax Invoice Issuance Info' → 'Billing Information'
* 'Business Registration Number' → 'TAX ID'
## Popup Ad Performance Optimization
You can now add a CTA (Call-to-Action button) to the bottom right of popup ads. This ad UX has been proven to improve click-through rates by approximately 30%.
**Setup Method**
* Turn on advanced options in popup ad units to select button template type
* Select ad performance optimization option and choose CTA button position
* Advertisers can choose from default label options for their desired CTA text
***
## Popup Carousel Ad Limit
You can now set the number of ads to display in the carousel as desired. Protect user experience by showing only the necessary ads while also improving ad performance.
**Setup Method**
* Enable **Carousel Ad Limit** toggle in popup ad unit template options
* Enter **Maximum Ad Count**
**Caution**
* When selling popup ad units as CPP slots, set the carousel ad limit higher than the number of slots
## Campaign Duplication
Easily create new campaigns by duplicating existing ones.
**Basic Usage**
* Campaign detail page top-right 3-dot menu (⋮) → Select \[Duplicate Campaign]
* Campaign duplication is not possible if units in the ad product have been deleted or if modifications are needed in the unit set
**Campaign Duplication in Ad Account**
* Duplicated campaigns are saved in 'Draft' status
* Draft campaigns are not visible in the Console
* Status changes to 'Under Review' only after agreeing to campaign terms and final submission
**Campaign Duplication in Console**
* Duplicated campaigns are visible in Ad Account even in 'Draft' status
* Manual payment campaigns are automatically approved without review after final submission
* Card payment campaigns can be finally submitted after the advertiser completes card payment in the Ad Account
***
## International Payments
International advertisers can now pay for advertising using Stripe.
**Key Policies**
* International payment fee: 5.5%
* Final settlement amount paid monthly after deducting payment fees
## Technical Support Inquiry Feature
You can now directly submit SDK/API integration, ad delivery, console usage, and other technical inquiries from the console.
***
## Country-specific App Search and Japan Market Support
**App Search Improvements**
* Country selection dropdown added (Korea, India, Japan)
* Changing country automatically reflects that country's app store search results
**Japan Market Support**
* Japanese Yen (¥) currency added
* Japanese consumption tax 10% automatically applied
* Tokyo timezone support
**Localization Settings Reorganization**
* Currency setting not required - automatically processed on country selection
* If receiving ad revenue in multiple currencies, each currency is displayed separately (no conversion)
***
## Campaign Management Improvements
* Campaign pause/resume: Now available for advertisers as well
* Creative pause: Individual ad creatives can be paused/resumed instead of entire campaign, also available for advertisers
***
## Enhanced Impression and Click Auto-adjustment System
* Time-based delivery settings accurately reflected (e.g., 9 AM - 6 PM only)
* Day-based delivery settings reflected (e.g., weekends excluded)
* Partial hours on start/end dates calculated precisely
* More impressions allocated at campaign start
* Unspent budget automatically redistributed next day
***
## Targeting Condition Setup UX Improvements
* Multiple selection support: Multiple targeting attributes can be selected for default properties (country, language, gender)
* Helpful tooltips added: Operator explanations, attribute descriptions/tracking methods/input examples provided
* Settings page UI improvements
* Targeting population collection method improvements
* Audience targeting Predefined Properties provided
* Backfill report added to Reports tab
* Console URL Endpoint changed
* Adrop website renewal
* Sign up/Login screen redesigned
* Quest ad product support ended
* Ad units can be managed separately on the Ad Units page
* Ad products page view format changed (table view)
* Ad product display order management improved
* Campaign/creative restart delay minimized (5 min → immediate)
* Custom event targeting feature paused
* Direct access to ad platform available (left sidebar)
* Campaign timeline view supported in Booking Management tab
* Ad products containing conflicting unit sets cannot be booked for campaigns
* Video ads supported (video creative support)
* Creative library supported
* Unit-specific creative registration: Manage same-type units as sets
* Enhanced creative display scheduling
* View my device supported in Web API
* Name and premium rate of targeting can be changed anytime for ongoing campaigns
* CC email address addition in settlement emails
* Daily quota modification before campaign starts
* Campaign progress visibility improved in booking management list
* Interstitial ad delay feature (close button display time setting)
* View on my device feature
* Quest product customization feature added
* Settlement month confirmation in Quest report added
## Global Support Enhancement
* Ad accounts (advertisers) can now set locale
* Publishers can set locale (region, timezone, currency) per ad product
## Global (India) Support
* VAT applied differently based on country setting
* Campaign operation timezone setting
* Default currency setting (KRW or INR)
* Project language setting
* Targeting category limit relaxed (up to 120 can be created)
* Report data CSV export supported
* Reports tab added
* "Matches any of" integer and float type support
* Contextual targeting supported
* Unit information Excel file download supported
* Quest performance can be checked via Quest report
* Campaign name can be modified in Console
* Creative preview feature activation status can be checked in advanced options
* Supported OS guidance when creating Quest ad products
* Last update time displayed in campaign detail modal performance report
* Internal/external browser selection available per creative on click
* Individual creative pause/resume available from publisher Console
* Ad product display order changeable
* Ad product introduction URL format insertable
* Additional targeting can be specified for ad products beyond required targeting
* Wave plan price increased from 210 won to 280 won
* Required targeting ad product population check
* Targeting information viewable in ad product modal
* CPQ campaign booking, creative registration, performance tracking, settlement confirmation
* Member roles visually identifiable
* Advertiser launcher and publisher launcher mobile responsive
* Campaign creative preview feature (add my device)
* CPQ product creation and modification
* CPQ performance check and settlement confirmation/print
* Advanced settings feature added (CPP budget change, creative time setting, audience population)
* Anyone can book within 7 days
* Test campaign validity time (30 min) setting
* Forced targeting setting for products
# Android
Source: https://docs.adrop.io/release-notes/android
Android SDK Updates
* Privacy options API added (privacy settings entry point)
* Consent handling stabilization
* Ad response latency minimized and loading speed improved
* Backfill rewarded ad onPaidEvent callback now passes user ID
* Ad revenue callback added (banner / interstitial / rewarded / native)
* Banner ad stabilization
* Banner backfill stabilization
* Splash ad stabilization
* Interstitial/rewarded ad UI stabilization
* Native ad stabilization
* Marketing consent API
* Backfill native ad AdChoices position customization
* Creative type getter
* Banner ad stabilization
* Batch ad loading API (loads) added
* Native ad stabilization
* Video ad stabilization
* Native ad stabilization
* MP4 video ad support
* Added video playback control methods for banner ads (`play()` / `pause()`)
* Backfill ad stabilization
* Interstitial ad stabilization
* Rewarded ad server-side verification (SSV) support
* User property auto-reset on UID change
* User event API support (sendEvent)
* Native ad stabilization
* Added interstitial ad back button callback
* Added browserTarget field
* SDK stabilization
* UMP integration (GDPR, CCPA consent popup API added)
* Native ads stabilization
* Backfill ads support (Banner / Native / Interstitial / Rewarded)
* Backfill ads support (Banner / Native)
* Banner video ads stabilization
* Creative dark mode support
* Popup ad performance optimization (CTA button added)
* Popup carousel ad limit
* Interface added to click callback
* Phone permission removed
* Carousel banner impression tracking improved
* Video ads support (video creative support)
* Stabilization when AdropNativeAdView isEntireClick is enabled
* Fixed DAU counting issue caused by push messages
* AdropNativeAd useCustomClick support
* AdropQuestBanner "from" property added
* Fixed AdropPopupAd destinationURL opening twice
* Amazing Quest URL processing logic changed
* Proguard issue fixed
* AdropBanner, AdropNativeAd contextual targeting support
* AdropBanner, AdropNativeAd creative width, height accessible
* onAdClicked callback available on AdropNativeAd click event
* Version info displayed on Amazing Quest settings page
* Adrop.openQuest(channel, path) function added
* Deposit top-up available in Amazing Quest
* Popup ad creativeId property added
* Popup ad onAdImpression, onAdClicked callbacks now pass destinationURL, creativeId
* AdropNativeAdListener onAdImpression callback added
* Crash handling for ad clicks in Flutter, React Native SDK
* Landing method applied based on browser target on ad click
* Crash bug fixed when opening AdropQuestActivity
* Splash ad deep link support
* AdropQuestBanner added
* "Preview on my device" feature support
* Popup ad template feature support
* Popup ad "Don't show today" bug fixed
* Popup ad "Don't show today" performance report
* Gradle v7.4.2 or higher support
* Banner carousel feature
* Adrop SDK lightweight optimization
* Stop rolling on popup close
* Splash ad optimization
* Popup ad infinite scroll
* Adrop.setUID() function added
* Popup ad custom size support
* Server latency improved, stability improved
* Stability improvements
* close() function added to AdropPopupAd
* AdropMetrics.setProperty value type changed to Nullable
* AdropMetrics.setProperty value type support (String, Int, Double, Boolean)
* AdropBanner no longer reloads on click
* shouldAdjustHtmlStyle set to true when using AdropBanner in AdMob custom adapter
* AdropSplashAdView - displayDuration field added
* ERROR\_CODE\_ACCOUNT\_USAGE\_LIMIT\_EXCEEDED error returned when Ad Control account limit exceeded
* Impression measurement enhanced
* AdropNativeAd - extra field added, asset field added
* AdropSplashAdView added (splash view custom integration)
# Flutter
Source: https://docs.adrop.io/release-notes/flutter
Flutter SDK Updates
* Privacy options API added (privacy settings entry point)
* Consent handling stabilization
* Ad response latency minimized and loading speed improved
* Backfill rewarded ad onPaidEvent callback now passes user ID
* Ad revenue callback added (banner / interstitial / rewarded / native)
* \[iOS] Swift Package Manager (SPM) support
* Batch ad loading API (loads) added
* Banner ad stabilization
* Interstitial / rewarded / native ad stabilization
* Native ad stabilization
* Native ad stabilization
* Marketing consent API
* Backfill native ad AdChoices position customization
* Creative type getter
* MP4 video ad support
* Backfill ad stabilization
* Interstitial ad stabilization
* Rewarded ad server-side verification (SSV) support
* User property auto-reset on UID change
* User event API support (sendEvent)
* Added interstitial ad back button callback
* Added browserTarget field
* UMP integration (GDPR, CCPA consent popup API added)
* Backfill ads support (Banner / Native / Interstitial / Rewarded)
* Video ads support (video creative support)
* AdropAdEventCallback changed
* onAdImpression added to AdropBannerListener, AdropNativeListener
* txId, campaignId added
* AdropPopupAd close function added
* AdropBannerView gesture handling improved
* AdropNativeAd useCustomClick support
* AdropBannerView performance optimization
* Fixed onAdClicked callback not firing on AdropNativeAd
* Android 1.3.14 compatibility
* AdropBannerView, AdropNativeAd creative width, height accessible
* onAdClicked callback available on AdropNativeAd click event
* Multiple AdropBannerViews with the same unit ID can be used
* Requires iOS, Android 1.3.0 or higher (preview on my device feature)
* Popup ad template feature support
* WebView support using native ad creative
* Android Gradle v7.4.2 or higher support
* Fixed issue where SDK couldn't be used on Flutter versions prior to 3.27.0
* Adrop.setUID() function support
* destinationURL accessible in Native Ad
* Adrop SDK updated
* AdropMetrics.properties() function support
* Native Ad support
* \[iOS] Setting to open ad clicks in in-app browser
* AdropMetrics.setProperty value multi-type support (String, Int, Double, Boolean)
* Ad creative id accessible
# iOS
Source: https://docs.adrop.io/release-notes/ios
iOS SDK Updates
* Privacy options API added (privacy settings entry point)
* Consent handling stabilization
* Ad response latency minimized and loading speed improved
* Backfill rewarded ad onPaidEvent callback now passes user ID
* Ad revenue callback added (banner / interstitial / rewarded / native)
* Banner ad stabilization
* Interstitial / rewarded / native ad stabilization
* Popup ad stabilization
* Backfill native ad stabilization
* Marketing consent API
* Backfill native ad AdChoices position customization
* Creative type getter
* Batch ad loading API (loads) added
* Native ad stabilization
* MP4 video ad support
* Added video playback control methods for banner ads (`play()` / `pause()`)
* Backfill ad stabilization
* Interstitial ad stabilization
* Rewarded ad server-side verification (SSV) support
* User property auto-reset on UID change
* User event API support (sendEvent)
* Added browserTarget field
* UMP integration (GDPR, CCPA consent popup API added)
* Backfill ads support (Interstitial / Rewarded)
* Objective-C splash ads support
* Backfill ads support (Banner / Native)
* Creative dark mode support
* Popup ad performance optimization (CTA button added)
* Popup carousel ad limit
* Interface added to click callback
* Banner video ads stabilization
* Carousel banner impression tracking improved
* Video ads support (video creative support)
* AdropBanner carousel stabilization
* adrop\_service.json validity (platform) check
* AdropNativeAd useCustomClick support
* AdropQuestBanner "from" property added
* Amazing Quest URL processing logic changed
* AdropBanner, AdropNativeAd contextual targeting support
* AdropBanner, AdropNativeAd creative width, height accessible
* onAdClicked callback available on AdropNativeAd click event
* Version info displayed on Amazing Quest settings page
* Popup ad: ad.destinationURL value populated on onAdImpression callback
* Adrop.openQuest(channel, path) function added
* Deposit top-up available in Amazing Quest
* Popup ad onAdImpression, onAdClicked callbacks now pass destinationURL, creativeId
* Support for applying adrop-related delegates to the same instance
* AdropNativeAdDelegate onImpression callback added
* Deep link implementation for direct access to Amazing products
* Landing method applied based on browser target on ad click
* AdropQuestBanner added
* "Preview on my device" feature support
* Fixed unintended top inset issue in native ad media view
* Multiple adrop\_service.json files can be used in one XCode project
* Popup ad stability improvements
* Popup ad template feature support
* Popup ad "Don't show today" performance report
* Fixed custom click not working in banner carousel
* Banner carousel feature
* Fixed banner ads not loading when returning from long background time
* Popup ad transparent background support
* Stop rolling on popup close
* Splash ad optimization
* Popup ad infinite scroll
* Adrop.setUID() function added
* Popup ad custom size support
* Popup ad auto rolling (3 seconds)
* Server latency improved, stability improved
* Stability improvements
* Splash ad display duration set from Adrop Console settings
* close() function added to AdropPopupAd
* AdropMetrics.setProperty value type support (String, Int, Double, Boolean)
* Fixed issue where ad destination URL to App Store didn't work on simulator
* Popup ad max height set to 520px or less & not exceeding screen size (landscape/iPad resolution support)
* AdropBanner no longer reloads on click
* useInAppBrowser parameter added to Adrop.initialize function
* AdropSplashAdView - displayDuration field added
* ERROR\_CODE\_ACCOUNT\_USAGE\_LIMIT\_EXCEEDED error returned when Ad Control account limit exceeded
* AdropNativeAd - extra field added, asset field added
* AdropSplashAdView, AdropSplashAdViewDelegate added (splash view custom integration)
# React Native
Source: https://docs.adrop.io/release-notes/react-native
React Native SDK Updates
* Privacy options API added (privacy settings entry point)
* Consent handling stabilization
* Ad response latency minimized and loading speed improved
* Backfill rewarded ad onPaidEvent callback now passes user ID
* Ad revenue callback added (banner / interstitial / rewarded / native)
* Batch ad loading API (loads) added
* Banner ad stabilization
* Interstitial / rewarded / native ad stabilization
* Native ad stabilization
* Marketing consent API
* Backfill native ad AdChoices position customization
* Creative type getter
* SDK stabilization
* Interstitial ad stabilization
* MP4 video ad support
* Backfill ad stabilization
* Interstitial ad stabilization
* Rewarded ad server-side verification (SSV) support
* User property auto-reset on UID change
* User event API support (sendEvent)
* Added browserTarget field
* Native video ad stabilization
* Native ad stabilization
* Improved version compatibility
* UMP integration (GDPR, CCPA consent popup API added)
* Backfill ads support (Banner / Native / Interstitial / Rewarded)
* Popup ad close method support
* Banner, Popup useCustomClick support
* AdropBannerListener callback interface changed
* txId, campaignId support
* setUID method support
* Carousel banner impression tracking improved
* Video ads support (video creative support)
* AdropNative useCustomClick support
* \[iOS] AdropNativeAd stabilization
* Fixed crash when requesting AdropNativeAd before NativeModules initialization
* Android 1.3.14 compatibility
* Requires iOS, Android 1.3.0 or higher (preview on my device feature)
* Popup ad template feature support
* \[Android] Native ad tracking bug fixed
* iOS stability improvements
* Android Gradle v7.4.2 or higher support
* Adrop SDK updated
* AdropMetrics.properties() function support
* Available on Expo Android
* \[iOS] Property tracking issue
* isReady added to hook component
* setProperty type support (string, number, bool)
* \[iOS] useInAppBrowser parameter added to Adrop.initialize function
* AdropNativeAd - extra field added, asset field added
# Web
Source: https://docs.adrop.io/release-notes/web
Web SDK Updates
* Anonymous uid support (frequency cap and audience targeting for signed-out visitors)
* liveAds support (check real ads on localhost)
* Requests without a uid are now subject to the frequency cap. Remove any random per-page uid values
* Improved iframe ad height handling
* adId support (View on Device)
* Backfill rendering mode support
* Mobile ad interaction stabilization
* MP4 video ad support
* User event API support (sendEvent)
* useCustomClick support
* SDK stabilization
* Backfill ads stabilization
* Banner, Native direct ads support
* Banner, Native backfill ads support
WebSDK is no longer supported. Please use REST API integration.
* setProperty type support (string, int, double, bool)
# Banner Ads
Source: https://docs.adrop.io/sdk/flutter/banner
Learn how to implement banner ads in your Flutter app.
## Overview
Banner ads are rectangular ads displayed in a portion of the screen. They can be easily implemented using the `AdropBannerView` widget.
### Key Features
* Can be fixed at the top, bottom, or middle of the screen
* Supports image and video ads
* Handle ad events through callbacks
* Provides ad metadata (creative ID, campaign ID, etc.)
Use the test unit ID for development: `PUBLIC_TEST_UNIT_ID_320_100`
***
## AdropBannerView
### Constructor
```dart theme={null}
AdropBannerView({
required String unitId,
AdropBannerListener? listener,
})
```
**Parameters**
| Parameter | Type | Required | Description |
| - | - | - | - |
| `unitId` | `String` | Y | Unit ID created in the Ad Control Console |
| `listener` | `AdropBannerListener` | N | Ad event listener |
### Properties
| Property | Type | Description |
| - | - | - |
| `creativeSize` | `CreativeSize?` | Ad creative size |
| `adSize` | `Size?` | Banner view size setting |
### Methods
| Method | Return Type | Description |
| - | - | - |
| `load()` | `Future` | Loads the ad |
| `dispose()` | `Future` | Releases resources |
***
## Basic Usage
```dart theme={null}
import 'package:flutter/material.dart';
import 'package:adrop_ads_flutter/adrop_ads_flutter.dart';
class BannerExample extends StatefulWidget {
const BannerExample({super.key});
@override
State createState() => _BannerExampleState();
}
class _BannerExampleState extends State {
bool isLoaded = false;
late AdropBannerView bannerView;
@override
void initState() {
super.initState();
bannerView = AdropBannerView(
unitId: 'YOUR_UNIT_ID',
listener: AdropBannerListener(
onAdReceived: (unitId, metadata) {
debugPrint('Banner ad received: $unitId');
setState(() {
isLoaded = true;
});
},
onAdClicked: (unitId, metadata) {
debugPrint('Banner ad clicked: $unitId');
},
onAdImpression: (unitId, metadata) {
debugPrint('Banner ad impression: $unitId');
},
onAdFailedToReceive: (unitId, errorCode) {
debugPrint('Banner ad failed: $unitId, $errorCode');
setState(() {
isLoaded = false;
});
},
),
);
// Load ad
bannerView.load();
}
@override
void dispose() {
bannerView.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Banner Ad Example')),
body: Column(
children: [
// Main content
Expanded(
child: Center(
child: const Text('Main Content'),
),
),
// Banner ad
if (isLoaded)
SizedBox(
width: MediaQuery.of(context).size.width,
height: 80,
child: bannerView,
),
],
),
);
}
}
```
***
## AdropBannerListener
A listener that handles banner ad events.
### Callbacks
```dart theme={null}
AdropBannerListener(
onAdReceived: (String unitId, Map? metadata) {
// Ad received successfully
},
onAdClicked: (String unitId, Map? metadata) {
// Ad clicked
},
onAdImpression: (String unitId, Map? metadata) {
// Ad impression
},
onAdFailedToReceive: (String unitId, AdropErrorCode errorCode) {
// Ad failed to receive
},
onAdVideoStart: (String unitId) {
// Video ad started playing
},
onAdVideoEnd: (String unitId) {
// Video ad finished playing
},
)
```
### Callback Descriptions
| Callback | Description |
| - | - |
| `onAdReceived` | Called when ad is received successfully |
| `onAdClicked` | Called when ad is clicked |
| `onAdImpression` | Called when ad is displayed |
| `onAdFailedToReceive` | Called when ad fails to load |
| `onAdVideoStart` | Called when video ad starts playing |
| `onAdVideoEnd` | Called when video ad finishes playing |
### Metadata
You can receive metadata in the `onAdReceived`, `onAdClicked`, and `onAdImpression` callbacks.
```dart theme={null}
onAdReceived: (unitId, metadata) {
debugPrint('Creative ID: ${metadata?['creativeId']}');
debugPrint('Transaction ID: ${metadata?['txId']}');
debugPrint('Campaign ID: ${metadata?['campaignId']}');
debugPrint('Destination URL: ${metadata?['destinationURL']}');
debugPrint('Browser Target: ${metadata?['browserTarget']}');
}
```
| Field | Type | Description |
| - | - | - |
| `creativeId` | `String` | Creative ID |
| `txId` | `String` | Transaction ID |
| `campaignId` | `String` | Campaign ID |
| `destinationURL` | `String` | Destination URL |
| `browserTarget` | `int?` | Browser target (0: external, 1: internal) |
Creative size is accessed via `bannerView.creativeSize?.width` and `bannerView.creativeSize?.height`, not through metadata.
***
## Ad Sizes
Banner ads require specifying container size according to the unit settings.
### Common Banner Sizes
| Size | Test Unit ID | Usage |
| - | - | - |
| 320 x 50 | `PUBLIC_TEST_UNIT_ID_320_50` | Small banner |
| 320 x 100 | `PUBLIC_TEST_UNIT_ID_320_100` | Medium banner |
### Fixed Size Usage
```dart theme={null}
SizedBox(
width: 320,
height: 100,
child: bannerView,
)
```
### Fit to Screen Width
```dart theme={null}
SizedBox(
width: MediaQuery.of(context).size.width,
height: 80,
child: bannerView,
)
```
### Size Configuration
You can explicitly set the banner view size using the `adSize` property.
```dart theme={null}
@override
void initState() {
super.initState();
bannerView = AdropBannerView(
unitId: 'YOUR_UNIT_ID',
listener: AdropBannerListener(
onAdReceived: (unitId, metadata) {
setState(() {
isLoaded = true;
});
},
),
);
// Set size after context is ready
WidgetsBinding.instance.addPostFrameCallback((_) {
bannerView.adSize = Size(
MediaQuery.of(context).size.width,
80,
);
});
}
```
***
## Error Handling
Implement proper error handling when ad loading fails.
```dart theme={null}
class _BannerExampleState extends State {
bool isLoaded = false;
AdropErrorCode? errorCode;
late AdropBannerView bannerView;
@override
void initState() {
super.initState();
bannerView = AdropBannerView(
unitId: 'YOUR_UNIT_ID',
listener: AdropBannerListener(
onAdReceived: (unitId, metadata) {
setState(() {
isLoaded = true;
errorCode = null;
});
},
onAdFailedToReceive: (unitId, error) {
setState(() {
isLoaded = false;
errorCode = error;
});
},
),
);
bannerView.load();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
if (isLoaded)
SizedBox(
width: MediaQuery.of(context).size.width,
height: 80,
child: bannerView,
)
else if (errorCode != null)
Text('Ad load failed: ${errorCode?.code}'),
],
);
}
@override
void dispose() {
bannerView.dispose();
super.dispose();
}
}
```
***
## Best Practices
### 1. Release Resources
Always call `dispose()` when the banner view is no longer needed.
```dart theme={null}
@override
void dispose() {
bannerView.dispose();
super.dispose();
}
```
### 2. Refresh Ads
You can refresh ads as needed.
```dart theme={null}
void refreshAd() {
setState(() {
isLoaded = false;
});
bannerView.load();
}
```
### 3. Conditional Rendering
Hide the banner area or show a placeholder until the ad loads.
```dart theme={null}
Widget buildBanner() {
if (isLoaded) {
return SizedBox(
width: MediaQuery.of(context).size.width,
height: 80,
child: bannerView,
);
} else if (isLoading) {
return const SizedBox(
height: 80,
child: Center(child: CircularProgressIndicator()),
);
} else {
return const SizedBox.shrink();
}
}
```
### 4. Managing Multiple Banners
When using multiple banners, manage each state separately.
```dart theme={null}
class _MultiBannerState extends State {
late AdropBannerView topBanner;
late AdropBannerView bottomBanner;
bool isTopLoaded = false;
bool isBottomLoaded = false;
@override
void initState() {
super.initState();
topBanner = AdropBannerView(
unitId: 'TOP_UNIT_ID',
listener: AdropBannerListener(
onAdReceived: (_, __) => setState(() => isTopLoaded = true),
),
);
bottomBanner = AdropBannerView(
unitId: 'BOTTOM_UNIT_ID',
listener: AdropBannerListener(
onAdReceived: (_, __) => setState(() => isBottomLoaded = true),
),
);
topBanner.load();
bottomBanner.load();
}
@override
void dispose() {
topBanner.dispose();
bottomBanner.dispose();
super.dispose();
}
// ...
}
```
***
## Complete Example
```dart theme={null}
import 'package:flutter/material.dart';
import 'package:adrop_ads_flutter/adrop_ads_flutter.dart';
class BannerAdPage extends StatefulWidget {
const BannerAdPage({super.key});
@override
State createState() => _BannerAdPageState();
}
class _BannerAdPageState extends State {
static const double bannerHeight = 80;
bool isLoaded = false;
bool isLoading = false;
AdropErrorCode? errorCode;
late AdropBannerView bannerView;
@override
void initState() {
super.initState();
bannerView = AdropBannerView(
unitId: 'PUBLIC_TEST_UNIT_ID_320_100', // Test unit ID
listener: AdropBannerListener(
onAdReceived: (unitId, metadata) {
debugPrint('Ad received successfully');
debugPrint('Creative ID: ${metadata?['creativeId']}');
debugPrint('Campaign ID: ${metadata?['campaignId']}');
debugPrint('Creative size: ${bannerView.creativeSize?.width}x${bannerView.creativeSize?.height}');
setState(() {
isLoaded = true;
isLoading = false;
errorCode = null;
});
},
onAdClicked: (unitId, metadata) {
debugPrint('Ad clicked: ${metadata?['destinationURL']}');
},
onAdImpression: (unitId, metadata) {
debugPrint('Ad impression: ${metadata?['creativeId']}');
},
onAdFailedToReceive: (unitId, error) {
debugPrint('Ad failed: $error');
setState(() {
isLoaded = false;
isLoading = false;
errorCode = error;
});
},
),
);
WidgetsBinding.instance.addPostFrameCallback((_) {
bannerView.adSize = Size(
MediaQuery.of(context).size.width,
bannerHeight,
);
});
}
void loadAd() {
setState(() {
isLoading = true;
errorCode = null;
});
bannerView.load();
}
@override
void dispose() {
bannerView.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Banner Ad Example'),
),
body: SafeArea(
child: Column(
children: [
// Load button
Padding(
padding: const EdgeInsets.all(16),
child: ElevatedButton(
onPressed: isLoading ? null : loadAd,
child: Text(isLoading ? 'Loading...' : 'Load Banner Ad'),
),
),
// Status display
if (errorCode != null)
Padding(
padding: const EdgeInsets.all(16),
child: Text(
'Error: ${errorCode?.code}',
style: const TextStyle(color: Colors.red),
),
),
// Main content
const Expanded(
child: Center(
child: Text('Main Content Area'),
),
),
// Banner ad
Container(
width: MediaQuery.of(context).size.width,
height: bannerHeight,
color: Colors.grey[200],
child: isLoaded
? bannerView
: isLoading
? const Center(child: CircularProgressIndicator())
: const Center(child: Text('Ad Area')),
),
],
),
),
);
}
}
```
***
## Backfill Ads
When backfill ads are enabled, backfill ads are automatically loaded when direct ads are unavailable. Backfill ad loading is handled automatically by the SDK. You can handle backfill-related errors using the `backfillNoFill` error code.
```dart theme={null}
bannerView = AdropBannerView(
unitId: 'YOUR_UNIT_ID',
listener: AdropBannerListener(
onAdReceived: (unitId, metadata) {
debugPrint('Ad loaded');
setState(() {
isLoaded = true;
});
},
onAdFailedToReceive: (unitId, errorCode) {
switch (errorCode) {
case AdropErrorCode.adNoFill:
debugPrint('No direct ad available');
break;
case AdropErrorCode.backfillNoFill:
debugPrint('No backfill ad available either');
// Hide ad area
break;
default:
debugPrint('Ad load failed: ${errorCode.code}');
}
},
),
);
```
Banner ad metadata only includes the `creativeId`, `txId`, `campaignId`, `destinationURL`, and `browserTarget` fields. Backfill ads cannot be distinguished through metadata. You can detect backfill ad failures using the `backfillNoFill` error code.
To use backfill ads, add the backfill dependency to native platforms. See [Getting Started](/sdk/flutter/overview).
***
## Next Steps
Implement customizable native ads that match your UI
Implement full-screen interstitial ads
Implement popup-style ads
View types, methods, and error codes
# Examples
Source: https://docs.adrop.io/sdk/flutter/examples
Example code and sample projects for the Flutter SDK.
## Overview
Check out example code and sample projects for the Adrop Flutter SDK.
***
## Sample Project
View the complete sample project on GitHub.
Flutter sample app written in Dart
***
## Quick Start Examples
### Banner Ad
```dart theme={null}
import 'package:flutter/material.dart';
import 'package:adrop_ads_flutter/adrop_ads_flutter.dart';
class BannerAdExample extends StatefulWidget {
const BannerAdExample({super.key});
@override
State createState() => _BannerAdExampleState();
}
class _BannerAdExampleState extends State {
bool isLoaded = false;
late AdropBannerView bannerView;
@override
void initState() {
super.initState();
bannerView = AdropBannerView(
unitId: 'PUBLIC_TEST_UNIT_ID_320_100',
listener: AdropBannerListener(
onAdReceived: (unitId, metadata) {
debugPrint('Banner ad received: $unitId');
setState(() => isLoaded = true);
},
onAdClicked: (unitId, metadata) {
debugPrint('Banner ad clicked: $unitId');
},
onAdFailedToReceive: (unitId, errorCode) {
debugPrint('Banner ad failed to receive: $errorCode');
},
),
);
bannerView.load();
}
@override
void dispose() {
bannerView.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Banner Ad')),
body: Column(
children: [
Expanded(
child: Center(child: const Text('Main Content')),
),
if (isLoaded)
SizedBox(
width: MediaQuery.of(context).size.width,
height: 80,
child: bannerView,
),
],
),
);
}
}
```
***
### Native Ad
```dart theme={null}
import 'package:flutter/material.dart';
import 'package:adrop_ads_flutter/adrop_ads_flutter.dart';
class NativeAdExample extends StatefulWidget {
const NativeAdExample({super.key});
@override
State createState() => _NativeAdExampleState();
}
class _NativeAdExampleState extends State {
bool isLoaded = false;
AdropNativeAd? nativeAd;
@override
void initState() {
super.initState();
nativeAd = AdropNativeAd(
unitId: 'PUBLIC_TEST_UNIT_ID_NATIVE',
listener: AdropNativeListener(
onAdReceived: (ad) {
debugPrint('Native ad received: ${ad.creativeId}');
setState(() => isLoaded = true);
},
onAdClicked: (ad) {
debugPrint('Native ad clicked');
},
onAdFailedToReceive: (ad, errorCode) {
debugPrint('Native ad failed to receive: $errorCode');
},
),
);
nativeAd?.load();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Native Ad')),
body: SingleChildScrollView(
child: Column(
children: [
const Padding(
padding: EdgeInsets.all(16),
child: Text('Main Content'),
),
if (isLoaded) _buildNativeAdView(),
],
),
),
);
}
Widget _buildNativeAdView() {
return AdropNativeAdView(
ad: nativeAd,
child: Container(
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Profile
Row(
children: [
if (nativeAd?.properties.profile?.displayLogo != null)
ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Image.network(
nativeAd!.properties.profile!.displayLogo!,
width: 24,
height: 24,
),
),
const SizedBox(width: 8),
Text(
nativeAd?.properties.profile?.displayName ?? '',
style: const TextStyle(
fontSize: 12,
color: Colors.grey,
),
),
],
),
const SizedBox(height: 12),
// Headline
if (nativeAd?.properties.headline != null)
Text(
nativeAd!.properties.headline!,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
// Body
if (nativeAd?.properties.body != null)
Text(
nativeAd!.properties.body!,
style: const TextStyle(
fontSize: 14,
color: Colors.grey,
),
),
const SizedBox(height: 12),
// Image
if (nativeAd?.properties.asset != null)
ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Image.network(
nativeAd!.properties.asset!,
width: double.infinity,
fit: BoxFit.cover,
),
),
const SizedBox(height: 12),
// CTA Button
if (nativeAd?.properties.callToAction != null)
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {},
child: Text(nativeAd!.properties.callToAction!),
),
),
],
),
),
);
}
}
```
***
### Interstitial Ad
```dart theme={null}
import 'package:flutter/material.dart';
import 'package:adrop_ads_flutter/adrop_ads_flutter.dart';
class InterstitialAdExample extends StatefulWidget {
const InterstitialAdExample({super.key});
@override
State createState() => _InterstitialAdExampleState();
}
class _InterstitialAdExampleState extends State {
bool isLoaded = false;
AdropInterstitialAd? interstitialAd;
@override
void initState() {
super.initState();
_createInterstitialAd();
}
void _createInterstitialAd() {
interstitialAd?.dispose();
interstitialAd = AdropInterstitialAd(
unitId: 'PUBLIC_TEST_UNIT_ID_INTERSTITIAL',
listener: AdropInterstitialListener(
onAdReceived: (ad) {
debugPrint('Interstitial ad loaded');
setState(() => isLoaded = true);
},
onAdClicked: (ad) {
debugPrint('Interstitial ad clicked');
},
onAdDidPresentFullScreen: (ad) {
debugPrint('Interstitial ad presented');
},
onAdDidDismissFullScreen: (ad) {
debugPrint('Interstitial ad dismissed');
// Load new ad
_createInterstitialAd();
interstitialAd?.load();
},
onAdFailedToReceive: (ad, errorCode) {
debugPrint('Interstitial ad failed to load: $errorCode');
},
),
);
setState(() => isLoaded = false);
}
@override
void dispose() {
interstitialAd?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Interstitial Ad')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () => interstitialAd?.load(),
child: const Text('Load Ad'),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: isLoaded ? () => interstitialAd?.show() : null,
child: const Text('Show Ad'),
),
],
),
),
);
}
}
```
***
### Rewarded Ad
```dart theme={null}
import 'package:flutter/material.dart';
import 'package:adrop_ads_flutter/adrop_ads_flutter.dart';
class RewardedAdExample extends StatefulWidget {
const RewardedAdExample({super.key});
@override
State createState() => _RewardedAdExampleState();
}
class _RewardedAdExampleState extends State {
bool isLoaded = false;
int coins = 0;
AdropRewardedAd? rewardedAd;
@override
void initState() {
super.initState();
_createRewardedAd();
}
void _createRewardedAd() {
rewardedAd?.dispose();
rewardedAd = AdropRewardedAd(
unitId: 'PUBLIC_TEST_UNIT_ID_REWARDED',
listener: AdropRewardedListener(
onAdReceived: (ad) {
debugPrint('Rewarded ad loaded');
setState(() => isLoaded = true);
},
onAdDidDismissFullScreen: (ad) {
debugPrint('Rewarded ad dismissed');
// Load new ad
_createRewardedAd();
rewardedAd?.load();
},
onAdEarnRewardHandler: (ad, type, amount) {
debugPrint('Reward earned: type=$type, amount=$amount');
setState(() => coins += amount);
_showRewardSnackBar(amount);
},
onAdFailedToReceive: (ad, errorCode) {
debugPrint('Rewarded ad failed to load: $errorCode');
},
),
);
setState(() => isLoaded = false);
}
void _showRewardSnackBar(int amount) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('You earned $amount coins!'),
backgroundColor: Colors.green,
),
);
}
@override
void dispose() {
rewardedAd?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Rewarded Ad')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Coin display
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.amber.shade100,
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.monetization_on, color: Colors.amber),
const SizedBox(width: 8),
Text(
'$coins Coins',
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
],
),
),
const SizedBox(height: 32),
ElevatedButton(
onPressed: () => rewardedAd?.load(),
child: const Text('Load Ad'),
),
const SizedBox(height: 16),
ElevatedButton.icon(
onPressed: isLoaded ? () => rewardedAd?.show() : null,
icon: const Icon(Icons.play_circle_outline),
label: const Text('Watch Ad & Get Reward'),
style: ElevatedButton.styleFrom(
backgroundColor: isLoaded ? Colors.green : Colors.grey,
),
),
],
),
),
);
}
}
```
***
### Popup Ad
```dart theme={null}
import 'package:flutter/material.dart';
import 'package:adrop_ads_flutter/adrop_ads_flutter.dart';
class PopupAdExample extends StatefulWidget {
const PopupAdExample({super.key});
@override
State createState() => _PopupAdExampleState();
}
class _PopupAdExampleState extends State {
bool isLoaded = false;
AdropPopupAd? popupAd;
@override
void initState() {
super.initState();
_createPopupAd();
}
void _createPopupAd() {
popupAd?.dispose();
popupAd = AdropPopupAd(
unitId: 'PUBLIC_TEST_UNIT_ID_POPUP_BOTTOM',
closeTextColor: Colors.white,
hideForTodayTextColor: Colors.white70,
backgroundColor: Colors.black87,
listener: AdropPopupListener(
onAdReceived: (ad) {
debugPrint('Popup ad loaded');
setState(() => isLoaded = true);
},
onAdDidDismissFullScreen: (ad) {
debugPrint('Popup ad dismissed');
_createPopupAd();
},
onAdFailedToReceive: (ad, errorCode) {
debugPrint('Popup ad failed to load: $errorCode');
if (errorCode == AdropErrorCode.adHideForToday) {
debugPrint('User chose to hide for today.');
}
},
),
);
setState(() => isLoaded = false);
}
@override
void dispose() {
popupAd?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Popup Ad')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () => popupAd?.load(),
child: const Text('Load Ad'),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: isLoaded ? () => popupAd?.show() : null,
child: const Text('Show Ad'),
),
],
),
),
);
}
}
```
***
## Advanced Examples
### Dark Mode Support
```dart theme={null}
import 'package:flutter/material.dart';
import 'package:adrop_ads_flutter/adrop_ads_flutter.dart';
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State createState() => _MyAppState();
}
class _MyAppState extends State with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
_initializeAdrop();
}
Future _initializeAdrop() async {
await Adrop.initialize(true);
// Auto-follow system theme
await Adrop.setTheme(AdropTheme.auto);
}
@override
void didChangePlatformBrightness() {
// Handle system theme changes
// Automatically handled when using AdropTheme.auto
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
themeMode: ThemeMode.system,
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
home: const HomeScreen(),
);
}
}
```
***
### User Targeting Setup
```dart theme={null}
import 'package:flutter/material.dart';
import 'package:adrop_ads_flutter/adrop_ads_flutter.dart';
class UserProfileScreen extends StatelessWidget {
final User user;
const UserProfileScreen({super.key, required this.user});
Future _setupTargeting() async {
// Set basic properties
await AdropMetrics.setProperty('birth', user.birthDate);
await AdropMetrics.setProperty('gender', user.gender);
// Set custom properties
await AdropMetrics.setProperty('membership_level', user.membershipLevel);
await AdropMetrics.setProperty('total_purchase', user.totalPurchase);
await AdropMetrics.setProperty('is_premium', user.isPremium);
// Send event
await AdropMetrics.sendEvent('purchase', {
'item_id': 'SKU_001',
'price': 29900,
'currency': 'KRW',
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Profile')),
body: Center(
child: ElevatedButton(
onPressed: _setupTargeting,
child: const Text('Setup Targeting'),
),
),
);
}
}
class User {
final String id;
final String birthDate;
final String gender;
final String membershipLevel;
final int totalPurchase;
final bool isPremium;
User({
required this.id,
required this.birthDate,
required this.gender,
required this.membershipLevel,
required this.totalPurchase,
required this.isPremium,
});
}
```
***
### Ad Preloading
```dart theme={null}
import 'package:flutter/material.dart';
import 'package:adrop_ads_flutter/adrop_ads_flutter.dart';
class AdPreloader {
static AdropInterstitialAd? _interstitialAd;
static AdropRewardedAd? _rewardedAd;
static bool _isInterstitialReady = false;
static bool _isRewardedReady = false;
static void preloadInterstitial() {
_interstitialAd?.dispose();
_interstitialAd = AdropInterstitialAd(
unitId: 'YOUR_INTERSTITIAL_UNIT_ID',
listener: AdropInterstitialListener(
onAdReceived: (ad) {
_isInterstitialReady = true;
debugPrint('Interstitial ad ready');
},
onAdDidDismissFullScreen: (ad) {
_isInterstitialReady = false;
// Reload for next time
Future.delayed(const Duration(seconds: 1), preloadInterstitial);
},
onAdFailedToReceive: (ad, errorCode) {
_isInterstitialReady = false;
// Retry
Future.delayed(const Duration(seconds: 5), preloadInterstitial);
},
),
);
_interstitialAd?.load();
}
static void preloadRewarded() {
_rewardedAd?.dispose();
_rewardedAd = AdropRewardedAd(
unitId: 'YOUR_REWARDED_UNIT_ID',
listener: AdropRewardedListener(
onAdReceived: (ad) {
_isRewardedReady = true;
debugPrint('Rewarded ad ready');
},
onAdDidDismissFullScreen: (ad) {
_isRewardedReady = false;
Future.delayed(const Duration(seconds: 1), preloadRewarded);
},
onAdFailedToReceive: (ad, errorCode) {
_isRewardedReady = false;
Future.delayed(const Duration(seconds: 5), preloadRewarded);
},
),
);
_rewardedAd?.load();
}
static bool get isInterstitialReady => _isInterstitialReady;
static bool get isRewardedReady => _isRewardedReady;
static void showInterstitial() {
if (_isInterstitialReady) {
_interstitialAd?.show();
_isInterstitialReady = false;
}
}
static void showRewarded(
void Function(int type, int amount)? onEarnReward) {
if (_isRewardedReady) {
if (onEarnReward != null) {
_rewardedAd = AdropRewardedAd(
unitId: 'YOUR_REWARDED_UNIT_ID',
listener: AdropRewardedListener(
onAdEarnRewardHandler: (ad, type, amount) {
onEarnReward(type, amount);
},
onAdDidDismissFullScreen: (ad) {
_isRewardedReady = false;
preloadRewarded();
},
),
);
}
_rewardedAd?.show();
_isRewardedReady = false;
}
}
static void dispose() {
_interstitialAd?.dispose();
_rewardedAd?.dispose();
_interstitialAd = null;
_rewardedAd = null;
_isInterstitialReady = false;
_isRewardedReady = false;
}
}
// Call on app start
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Adrop.initialize(true);
// Preload ads
AdPreloader.preloadInterstitial();
AdPreloader.preloadRewarded();
runApp(const MyApp());
}
```
***
## Related Documentation
Banner ad implementation guide
Native ad implementation guide
Interstitial ad implementation guide
Rewarded ad implementation guide
# Interstitial Ads
Source: https://docs.adrop.io/sdk/flutter/interstitial
Learn how to implement interstitial ads in your Flutter app.
## Overview
Interstitial ads are full-screen ads that cover the entire screen. They are effective when displayed at natural transition points (e.g., after completing a game level, page transitions, etc.).
### Key Features
* Full-screen ads that cover the entire display
* Supports image and video ads
* Provides fullscreen event callbacks
* Flexible timing control with separate loading and showing
Use the test unit ID in development: `PUBLIC_TEST_UNIT_ID_INTERSTITIAL`
***
## AdropInterstitialAd
### Constructor
```dart theme={null}
AdropInterstitialAd({
required String unitId,
AdropInterstitialListener? listener,
})
```
**Parameters**
| Parameter | Type | Required | Description |
| - | - | - | - |
| `unitId` | `String` | Y | Unit ID created in Ad Control console |
| `listener` | `AdropInterstitialListener` | N | Ad event listener |
### Properties
| Property | Type | Description |
| - | - | - |
| `isLoaded` | `bool` | Whether the ad has finished loading |
| `unitId` | `String` | Ad unit ID |
| `creativeId` | `String` | Creative ID |
| `txId` | `String` | Transaction ID |
| `campaignId` | `String` | Campaign ID |
| `destinationURL` | `String` | Destination URL |
| `browserTarget` | `BrowserTarget?` | Browser target (external or internal) |
### Methods
| Method | Return Type | Description |
| - | - | - |
| `load()` | `Future` | Loads the ad |
| `show()` | `Future` | Displays the ad on screen |
| `close()` | `Future` | Programmatically closes the interstitial ad (Android only, no-op on iOS) |
| `dispose()` | `Future` | Releases resources |
When `close()` is called, the ad is dismissed and the `onAdDidDismissFullScreen` callback is triggered. If `close()` is not called in the `onAdBackButtonPressed` callback, the ad remains displayed and only the callback is received. Whether to close the ad is up to the developer.
***
## Basic Usage
```dart theme={null}
import 'package:flutter/material.dart';
import 'package:adrop_ads_flutter/adrop_ads_flutter.dart';
class InterstitialExample extends StatefulWidget {
const InterstitialExample({super.key});
@override
State createState() => _InterstitialExampleState();
}
class _InterstitialExampleState extends State {
bool isLoaded = false;
AdropInterstitialAd? interstitialAd;
@override
void initState() {
super.initState();
_createInterstitialAd();
}
void _createInterstitialAd() {
interstitialAd = AdropInterstitialAd(
unitId: 'YOUR_UNIT_ID',
listener: AdropInterstitialListener(
onAdReceived: (ad) {
debugPrint('Interstitial ad loaded successfully: ${ad.creativeId}');
setState(() {
isLoaded = true;
});
},
onAdFailedToReceive: (ad, errorCode) {
debugPrint('Interstitial ad failed to load: $errorCode');
},
onAdClicked: (ad) {
debugPrint('Interstitial ad clicked');
},
onAdDidPresentFullScreen: (ad) {
debugPrint('Interstitial ad presented');
},
onAdDidDismissFullScreen: (ad) {
debugPrint('Interstitial ad dismissed');
},
onAdFailedToShowFullScreen: (ad, errorCode) {
debugPrint('Interstitial ad failed to show: $errorCode');
},
onAdBackButtonPressed: (ad) {
debugPrint('Back button pressed');
},
),
);
}
void loadAd() {
interstitialAd?.load();
}
void showAd() {
if (isLoaded) {
interstitialAd?.show();
}
}
@override
void dispose() {
interstitialAd?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Interstitial Ad Example')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: loadAd,
child: const Text('Load Ad'),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: isLoaded ? showAd : null,
child: const Text('Show Ad'),
),
],
),
),
);
}
}
```
***
## AdropInterstitialListener
Listener to handle interstitial ad events.
### Callbacks
```dart theme={null}
AdropInterstitialListener(
onAdReceived: (AdropAd ad) {
// Ad loaded successfully
},
onAdClicked: (AdropAd ad) {
// Ad clicked
},
onAdImpression: (AdropAd ad) {
// Ad impression
},
onAdWillPresentFullScreen: (AdropAd ad) {
// About to present ad (iOS only)
},
onAdDidPresentFullScreen: (AdropAd ad) {
// Ad presented
},
onAdWillDismissFullScreen: (AdropAd ad) {
// About to dismiss ad (iOS only)
},
onAdDidDismissFullScreen: (AdropAd ad) {
// Ad dismissed
},
onAdFailedToReceive: (AdropAd ad, AdropErrorCode errorCode) {
// Ad failed to load
},
onAdFailedToShowFullScreen: (AdropAd ad, AdropErrorCode errorCode) {
// Ad failed to show
},
onAdBackButtonPressed: (AdropAd ad) {
// Back button pressed (Android only)
},
)
```
### Callback Descriptions
| Callback | Description |
| - | - |
| `onAdReceived` | Called when ad is loaded successfully |
| `onAdClicked` | Called when ad is clicked |
| `onAdImpression` | Called when ad impression occurs |
| `onAdWillPresentFullScreen` | Called before ad is presented (iOS only) |
| `onAdDidPresentFullScreen` | Called after ad is presented |
| `onAdWillDismissFullScreen` | Called before ad is dismissed (iOS only) |
| `onAdDidDismissFullScreen` | Called after ad is dismissed |
| `onAdFailedToReceive` | Called when ad fails to load |
| `onAdFailedToShowFullScreen` | Called when ad fails to show |
| `onAdBackButtonPressed` | Called when the user presses the back button while the ad is displayed (Android only) |
***
## Ad Lifecycle
```mermaid theme={null}
graph TD
A[Create] --> B[Call load]
B --> C{Load Result}
C -->|Success| D[onAdReceived]
C -->|Failure| E[onAdFailedToReceive]
D --> F[Call show]
F --> G{Show Result}
G -->|Success| H[onAdWillPresentFullScreen]
G -->|Failure| I[onAdFailedToShowFullScreen]
H --> J[onAdDidPresentFullScreen]
J --> K[User Interaction]
K -->|Click| L[onAdClicked]
K -->|Dismiss| M[onAdWillDismissFullScreen]
K -->|Back Button Android| P[onAdBackButtonPressed]
P -->|call close| M
M --> N[onAdDidDismissFullScreen]
N --> O[dispose or recreate]
```
***
## Ad Recreation
Interstitial ads are single-use. Once shown, they cannot be displayed again, so you must recreate the instance to load a new ad.
```dart theme={null}
class _InterstitialState extends State {
bool isLoaded = false;
bool isShown = false;
AdropInterstitialAd? interstitialAd;
@override
void initState() {
super.initState();
_createInterstitialAd();
}
void _createInterstitialAd() {
interstitialAd?.dispose();
interstitialAd = AdropInterstitialAd(
unitId: 'YOUR_UNIT_ID',
listener: AdropInterstitialListener(
onAdReceived: (ad) {
setState(() {
isLoaded = true;
});
},
onAdDidPresentFullScreen: (ad) {
setState(() {
isShown = true;
});
},
onAdDidDismissFullScreen: (ad) {
// Prepare new ad after dismissal
_createInterstitialAd();
interstitialAd?.load();
},
),
);
setState(() {
isLoaded = false;
isShown = false;
});
}
@override
void dispose() {
interstitialAd?.dispose();
super.dispose();
}
}
```
***
## Error Handling
```dart theme={null}
class _InterstitialState extends State {
bool isLoaded = false;
AdropErrorCode? errorCode;
AdropInterstitialAd? interstitialAd;
void _createInterstitialAd() {
interstitialAd = AdropInterstitialAd(
unitId: 'YOUR_UNIT_ID',
listener: AdropInterstitialListener(
onAdReceived: (ad) {
setState(() {
isLoaded = true;
errorCode = null;
});
},
onAdFailedToReceive: (ad, error) {
setState(() {
isLoaded = false;
errorCode = error;
});
_handleError(error);
},
onAdFailedToShowFullScreen: (ad, error) {
setState(() {
errorCode = error;
});
_handleError(error);
},
),
);
}
void _handleError(AdropErrorCode error) {
switch (error) {
case AdropErrorCode.network:
debugPrint('Network error occurred.');
break;
case AdropErrorCode.adNoFill:
debugPrint('No ad available to show.');
break;
case AdropErrorCode.adShown:
debugPrint('Ad has already been shown.');
break;
default:
debugPrint('Error occurred: ${error.code}');
}
}
@override
Widget build(BuildContext context) {
return Column(
children: [
ElevatedButton(
onPressed: interstitialAd?.load,
child: const Text('Load Ad'),
),
ElevatedButton(
onPressed: isLoaded ? interstitialAd?.show : null,
child: const Text('Show Ad'),
),
if (errorCode != null)
Text(
'Error: ${errorCode?.code}',
style: const TextStyle(color: Colors.red),
),
],
);
}
}
```
***
## Best Practices
### 1. Show at Appropriate Times
Display interstitial ads at natural transition points.
```dart theme={null}
// Good: After level completion
void onLevelComplete() {
showInterstitialAd();
navigateToNextLevel();
}
// Bad: Right after app start
void onAppStart() {
showInterstitialAd(); // Degrades user experience
}
```
### 2. Preload Ads
Preloading ads allows you to show them without delay when needed.
```dart theme={null}
class GameScreen extends StatefulWidget {
@override
State createState() => _GameScreenState();
}
class _GameScreenState extends State {
AdropInterstitialAd? interstitialAd;
bool isAdReady = false;
@override
void initState() {
super.initState();
_preloadAd();
}
void _preloadAd() {
interstitialAd = AdropInterstitialAd(
unitId: 'YOUR_UNIT_ID',
listener: AdropInterstitialListener(
onAdReceived: (ad) {
setState(() {
isAdReady = true;
});
},
onAdDidDismissFullScreen: (ad) {
// Preload next ad
_preloadAd();
},
),
);
interstitialAd?.load();
}
void showAdIfReady() {
if (isAdReady) {
interstitialAd?.show();
setState(() {
isAdReady = false;
});
}
}
@override
void dispose() {
interstitialAd?.dispose();
super.dispose();
}
}
```
### 3. Frequency Capping
Limit ad frequency for better user experience.
```dart theme={null}
class AdManager {
static int _interstitialCount = 0;
static const int _maxInterstitialsPerSession = 3;
static DateTime? _lastInterstitialTime;
static const Duration _minInterval = Duration(minutes: 2);
static bool canShowInterstitial() {
if (_interstitialCount >= _maxInterstitialsPerSession) {
return false;
}
if (_lastInterstitialTime != null) {
final elapsed = DateTime.now().difference(_lastInterstitialTime!);
if (elapsed < _minInterval) {
return false;
}
}
return true;
}
static void onInterstitialShown() {
_interstitialCount++;
_lastInterstitialTime = DateTime.now();
}
}
```
### 4. Resource Management
Always release unused ad instances.
```dart theme={null}
@override
void dispose() {
interstitialAd?.dispose();
super.dispose();
}
```
***
## Complete Example
```dart theme={null}
import 'package:flutter/material.dart';
import 'package:adrop_ads_flutter/adrop_ads_flutter.dart';
class InterstitialAdPage extends StatefulWidget {
const InterstitialAdPage({super.key});
@override
State createState() => _InterstitialAdPageState();
}
class _InterstitialAdPageState extends State {
bool isLoaded = false;
bool isLoading = false;
bool isShown = false;
AdropErrorCode? errorCode;
AdropInterstitialAd? interstitialAd;
@override
void initState() {
super.initState();
_createInterstitialAd();
}
void _createInterstitialAd() {
interstitialAd?.dispose();
interstitialAd = AdropInterstitialAd(
unitId: 'PUBLIC_TEST_UNIT_ID_INTERSTITIAL', // Test unit ID
listener: AdropInterstitialListener(
onAdReceived: (ad) {
debugPrint('Ad loaded successfully');
debugPrint('Creative ID: ${ad.creativeId}');
debugPrint('Campaign ID: ${ad.campaignId}');
setState(() {
isLoaded = true;
isLoading = false;
errorCode = null;
});
},
onAdClicked: (ad) {
debugPrint('Ad clicked: ${ad.destinationURL}');
},
onAdImpression: (ad) {
debugPrint('Ad impression: ${ad.creativeId}');
},
onAdWillPresentFullScreen: (ad) {
debugPrint('Ad will present');
},
onAdDidPresentFullScreen: (ad) {
debugPrint('Ad presented');
setState(() {
isShown = true;
});
},
onAdWillDismissFullScreen: (ad) {
debugPrint('Ad will dismiss');
},
onAdDidDismissFullScreen: (ad) {
debugPrint('Ad dismissed');
// Prepare new ad
_createInterstitialAd();
},
onAdFailedToReceive: (ad, error) {
debugPrint('Ad failed to load: $error');
setState(() {
isLoaded = false;
isLoading = false;
errorCode = error;
});
},
onAdFailedToShowFullScreen: (ad, error) {
debugPrint('Ad failed to show: $error');
setState(() {
errorCode = error;
});
},
onAdBackButtonPressed: (ad) {
debugPrint('Back button pressed');
},
),
);
setState(() {
isLoaded = false;
isShown = false;
errorCode = null;
});
}
void loadAd() {
setState(() {
isLoading = true;
errorCode = null;
});
interstitialAd?.load();
}
void showAd() {
if (isLoaded) {
interstitialAd?.show();
}
}
@override
void dispose() {
interstitialAd?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Interstitial Ad Example'),
),
body: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Load button
ElevatedButton(
onPressed: isLoading ? null : loadAd,
child: Text(isLoading ? 'Loading...' : 'Load Ad'),
),
const SizedBox(height: 16),
// Show button
ElevatedButton(
onPressed: isLoaded ? showAd : null,
child: const Text('Show Ad'),
),
const SizedBox(height: 16),
// Reset button
TextButton(
onPressed: (isShown || errorCode != null)
? () => _createInterstitialAd()
: null,
child: const Text('Reset'),
),
const SizedBox(height: 24),
// Status display
Text('Loaded: ${isLoaded ? "Yes" : "No"}'),
Text('Shown: ${isShown ? "Yes" : "No"}'),
// Error display
if (errorCode != null) ...[
const SizedBox(height: 16),
Text(
'Error: ${errorCode?.code}',
style: const TextStyle(color: Colors.red),
),
],
],
),
),
),
);
}
}
```
***
## Backfill Ads
When backfill ads are enabled, backfill ads are automatically loaded when direct ads are unavailable. You can handle backfill-related errors using the error code.
```dart theme={null}
interstitialAd = AdropInterstitialAd(
unitId: 'YOUR_UNIT_ID',
listener: AdropInterstitialListener(
onAdReceived: (ad) {
debugPrint('Ad loaded: ${ad.creativeId}');
},
onAdFailedToReceive: (ad, errorCode) {
switch (errorCode) {
case AdropErrorCode.adNoFill:
debugPrint('No direct ad available');
break;
case AdropErrorCode.backfillNoFill:
debugPrint('No backfill ad available either');
break;
default:
debugPrint('Ad load failed: ${errorCode.code}');
}
},
),
);
```
To use backfill ads, add the backfill dependency to native platforms. See [Getting Started](/sdk/flutter/overview).
***
## Next Steps
Implement rewarded ads that provide rewards
Implement popup-style ads
Reference types, methods, and error codes
# Native Ads
Source: https://docs.adrop.io/sdk/flutter/native
Learn how to implement native ads in your Flutter app.
## Overview
Native ads are customizable ads that blend naturally with your app's content. You can configure the ad to match your app's UI using `AdropNativeAd` and `AdropNativeAdView`.
### Key Features
* Customizable layout to match app design
* Provides various elements like headline, body, CTA button, profile, etc.
* Supports image and HTML creative
* Custom click handling support
* Backfill ad support
Use the test unit ID in development: `PUBLIC_TEST_UNIT_ID_NATIVE`
***
## AdropNativeAd
### Constructor
```dart theme={null}
AdropNativeAd({
required String unitId,
bool useCustomClick = false,
AdropAdChoicesPosition preferredAdChoicesPosition = AdropAdChoicesPosition.topRight,
AdropNativeListener? listener,
})
```
**Parameters**
| Parameter | Type | Required | Description |
| - | - | - | - |
| `unitId` | `String` | Y | Unit ID created in Ad Control Console |
| `useCustomClick` | `bool` | N | Whether to use custom click handling (default: `false`) |
| `preferredAdChoicesPosition` | `AdropAdChoicesPosition` | N | AdChoices badge position for backfill native ads (default: `AdropAdChoicesPosition.topRight`) |
| `listener` | `AdropNativeListener` | N | Ad event listener |
### Properties
| Property | Type | Description |
| - | - | - |
| `isLoaded` | `bool` | Whether the ad has finished loading |
| `unitId` | `String` | Ad unit ID |
| `creativeId` | `String` | Creative ID |
| `txId` | `String` | Transaction ID |
| `campaignId` | `String` | Campaign ID |
| `destinationURL` | `String` | Destination URL |
| `properties` | `AdropNativeProperties` | Native ad properties |
| `creativeSize` | `CreativeSize` | Creative size |
| `isBackfilled` | `bool` | Whether it's a backfill ad |
| `browserTarget` | `BrowserTarget?` | Browser target (external or internal) |
### Methods
| Method | Return Type | Description |
| - | - | - |
| `load()` | `Future` | Loads the ad |
| `dispose()` | `Future` | Releases the underlying native WebView and, for backfill ads, the AdMob native ad |
Call `dispose()` when the native ad is no longer displayed. Failing to dispose leaks the native WebView and can lead to OOM in feed-style screens.
A disposed `AdropNativeAd` cannot be reused. To request another ad, create a new `AdropNativeAd` instance.
```dart theme={null}
@override
void dispose() {
_nativeAd?.dispose();
super.dispose();
}
```
***
## AdropNativeAdView
A widget that displays native ads on screen.
### Constructor
```dart theme={null}
AdropNativeAdView({
required AdropNativeAd? ad,
required Widget child,
})
```
**Parameters**
| Parameter | Type | Required | Description |
| - | - | - | - |
| `ad` | `AdropNativeAd?` | Y | Loaded native ad object |
| `child` | `Widget` | Y | Child widget to display ad content |
***
## AdropNativeProperties
Content properties of the native ad.
### Properties
| Property | Type | Description |
| - | - | - |
| `headline` | `String?` | Ad headline |
| `body` | `String?` | Ad body text |
| `creative` | `String?` | HTML creative content |
| `asset` | `String?` | Image asset URL |
| `destinationURL` | `String?` | URL to navigate on click |
| `callToAction` | `String?` | CTA button text |
| `profile` | `AdropNativeProfile?` | Advertiser profile information |
| `extra` | `Map` | Additional custom fields |
| `isBackfilled` | `bool` | Whether it's a backfill ad |
### AdropNativeProfile
| Property | Type | Description |
| - | - | - |
| `displayName` | `String?` | Advertiser name |
| `displayLogo` | `String?` | Advertiser logo image URL |
***
## Basic Usage
```dart theme={null}
import 'package:flutter/material.dart';
import 'package:adrop_ads_flutter/adrop_ads_flutter.dart';
class NativeAdExample extends StatefulWidget {
const NativeAdExample({super.key});
@override
State createState() => _NativeAdExampleState();
}
class _NativeAdExampleState extends State {
bool isLoaded = false;
AdropNativeAd? nativeAd;
@override
void initState() {
super.initState();
_createNativeAd();
}
@override
void dispose() {
nativeAd?.dispose();
nativeAd = null;
super.dispose();
}
void _createNativeAd() {
nativeAd = AdropNativeAd(
unitId: 'YOUR_UNIT_ID',
listener: AdropNativeListener(
onAdReceived: (ad) {
debugPrint('Native ad received successfully: ${ad.creativeId}');
setState(() {
isLoaded = true;
});
},
onAdClicked: (ad) {
debugPrint('Native ad clicked: ${ad.creativeId}');
},
onAdImpression: (ad) {
debugPrint('Native ad impression: ${ad.creativeId}');
},
onAdFailedToReceive: (ad, errorCode) {
debugPrint('Native ad failed to receive: $errorCode');
setState(() {
isLoaded = false;
});
},
),
);
// Load ad
nativeAd?.load();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Native Ad Example')),
body: SingleChildScrollView(
child: Column(
children: [
// Main content
const Padding(
padding: EdgeInsets.all(16),
child: Text('Main Content'),
),
// Native ad
if (isLoaded) _buildNativeAdView(),
],
),
),
);
}
Widget _buildNativeAdView() {
return AdropNativeAdView(
ad: nativeAd,
child: Container(
padding: const EdgeInsets.all(16),
margin: const EdgeInsets.all(16),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Advertiser profile
if (nativeAd?.properties.profile != null)
Row(
children: [
if (nativeAd?.properties.profile?.displayLogo != null)
Image.network(
nativeAd!.properties.profile!.displayLogo!,
width: 24,
height: 24,
),
const SizedBox(width: 8),
Text(nativeAd?.properties.profile?.displayName ?? ''),
],
),
const SizedBox(height: 8),
// Headline
if (nativeAd?.properties.headline != null)
Text(
nativeAd!.properties.headline!,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
// Body
if (nativeAd?.properties.body != null)
Text(nativeAd!.properties.body!),
const SizedBox(height: 8),
// Image asset
if (nativeAd?.properties.asset != null)
Image.network(
nativeAd!.properties.asset!,
width: double.infinity,
fit: BoxFit.cover,
),
const SizedBox(height: 8),
// CTA button
if (nativeAd?.properties.callToAction != null)
ElevatedButton(
onPressed: () {},
child: Text(nativeAd!.properties.callToAction!),
),
],
),
),
);
}
}
```
***
## AdropNativeListener
Listener to handle native ad events.
### Callbacks
```dart theme={null}
AdropNativeListener(
onAdReceived: (AdropNativeAd ad) {
// Ad received successfully
},
onAdClicked: (AdropNativeAd ad) {
// Ad clicked
},
onAdImpression: (AdropNativeAd ad) {
// Ad impression
},
onAdFailedToReceive: (AdropNativeAd ad, AdropErrorCode errorCode) {
// Ad failed to receive
},
onAdVideoStart: (AdropNativeAd ad) {
// Video ad started playing
},
onAdVideoEnd: (AdropNativeAd ad) {
// Video ad finished playing
},
)
```
### Callback Descriptions
| Callback | Description |
| - | - |
| `onAdReceived` | Called when ad is received successfully |
| `onAdClicked` | Called when ad is clicked |
| `onAdImpression` | Called when ad impression occurs |
| `onAdFailedToReceive` | Called when ad fails to receive |
| `onAdVideoStart` | Called when video ad starts playing |
| `onAdVideoEnd` | Called when video ad finishes playing |
***
## Custom Click Handling
Use `useCustomClick` when video creative or custom click behavior is needed.
```dart theme={null}
nativeAd = AdropNativeAd(
unitId: 'YOUR_UNIT_ID',
useCustomClick: true, // Enable custom click
listener: AdropNativeListener(
onAdReceived: (ad) {
setState(() {
isLoaded = true;
});
},
onAdClicked: (ad) {
// Handle custom click action
debugPrint('Ad clicked: ${ad.destinationURL}');
},
),
);
```
When `useCustomClick` is `true`, child widget click events are handled as ad clicks.
***
## Displaying HTML Creative
If the native ad includes HTML creative, you can display it using WebView.
For **video native creatives**, you must render `properties.creative` (the HTML payload) through a WebView. Passing `properties.asset` directly into `video_player` or any other custom player bypasses the SDK's video tracking pipeline, so **VTR is not measured** and `onAdVideoStart` / `onAdVideoEnd` never fire. See [Video Tracking and VTR](#video-tracking-and-vtr).
```dart theme={null}
import 'package:webview_flutter/webview_flutter.dart';
class NativeWithWebView extends StatefulWidget {
const NativeWithWebView({super.key});
@override
State createState() => _NativeWithWebViewState();
}
class _NativeWithWebViewState extends State {
bool isLoaded = false;
AdropNativeAd? nativeAd;
late final WebViewController webViewController;
@override
void initState() {
super.initState();
// Initialize WebView controller
webViewController = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted);
_createNativeAd();
}
void _createNativeAd() {
nativeAd = AdropNativeAd(
unitId: 'YOUR_UNIT_ID',
useCustomClick: true,
listener: AdropNativeListener(
onAdReceived: (ad) {
// Load HTML creative
if (ad.properties.creative != null) {
webViewController.loadHtmlString(ad.properties.creative!);
}
setState(() {
isLoaded = true;
});
},
),
);
nativeAd?.load();
}
@override
Widget build(BuildContext context) {
if (!isLoaded) return const SizedBox.shrink();
return AdropNativeAdView(
ad: nativeAd,
child: Container(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Profile
Row(
children: [
if (nativeAd?.properties.profile?.displayLogo != null)
Image.network(
nativeAd!.properties.profile!.displayLogo!,
width: 24,
height: 24,
),
const SizedBox(width: 8),
Text(nativeAd?.properties.profile?.displayName ?? ''),
],
),
const SizedBox(height: 8),
// Headline
if (nativeAd?.properties.headline != null)
Text(
nativeAd!.properties.headline!,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
// HTML creative
SizedBox(
width: MediaQuery.of(context).size.width,
height: 300,
child: WebViewWidget(controller: webViewController),
),
// CTA button
if (nativeAd?.properties.callToAction != null)
Padding(
padding: const EdgeInsets.only(top: 8),
child: ElevatedButton(
onPressed: () {},
child: Text(nativeAd!.properties.callToAction!),
),
),
],
),
),
);
}
}
```
To use HTML creative, you need to add the `webview_flutter` package.
```bash theme={null}
flutter pub add webview_flutter
```
***
## Handling Backfill Ads
You can check and handle backfill ads using the `isBackfilled` property.
```dart theme={null}
Widget _buildCreativeView() {
if (nativeAd?.isBackfilled == true) {
// Backfill ad: Use image asset
return Image.network(
nativeAd?.properties.asset ?? '',
width: double.infinity,
fit: BoxFit.cover,
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return const Center(child: CircularProgressIndicator());
},
errorBuilder: (context, error, stackTrace) {
return const Icon(Icons.error);
},
);
} else if (nativeAd?.properties.creative != null) {
// Regular ad: Use HTML creative
return SizedBox(
width: double.infinity,
height: 300,
child: WebViewWidget(controller: webViewController),
);
} else {
return const SizedBox.shrink();
}
}
```
### AdChoices Position
For backfill native ads, you can choose which corner displays the AdChoices badge. Set `preferredAdChoicesPosition` in the `AdropNativeAd` constructor.
```dart theme={null}
final nativeAd = AdropNativeAd(
unitId: 'YOUR_UNIT_ID',
preferredAdChoicesPosition: AdropAdChoicesPosition.bottomRight,
listener: AdropNativeListener(
onAdReceived: (ad) => debugPrint('Received'),
onAdFailedToReceive: (ad, errorCode) => debugPrint('Failed: $errorCode'),
),
);
await nativeAd.load();
```
| Value | Description |
| - | - |
| `AdropAdChoicesPosition.topLeft` | Top-left corner |
| `AdropAdChoicesPosition.topRight` | Top-right corner (default) |
| `AdropAdChoicesPosition.bottomLeft` | Bottom-left corner |
| `AdropAdChoicesPosition.bottomRight` | Bottom-right corner |
This setting is a preferred hint for the backfill network and applies only to backfill native ads — direct ads are not affected. The backfill network may override the position depending on its policy.
***
## Video Tracking and VTR
Adrop measures video metrics for native creatives — including impression-to-completion (VTR), `onAdVideoStart`, and `onAdVideoEnd` — only when the video is rendered through the SDK's media surface. On Flutter, that means rendering the HTML creative (`properties.creative`) inside a WebView, as shown in [Displaying HTML Creative](#displaying-html-creative).
The `properties.asset` field returns a URL pointing to the underlying image or video file. It is exposed as supplementary metadata (for example, for thumbnails or your own analytics); it is **not** intended to drive playback.
Do not feed the `properties.asset` URL into a custom video player (`video_player`, `chewie`, or any third-party player) for a video native ad. When the SDK's media surface is bypassed:
* **VTR (Video Through Rate) is not collected** for that placement.
* `onAdVideoStart` / `onAdVideoEnd` callbacks **never fire**.
* Aggregate video performance for the unit will under-report or report zero.
Click (`onAdClicked`) and impression (`onAdImpression`) tracking still work because they are wired to `AdropNativeAdView`, but the video-specific signals are lost.
### Recommended pattern
For video creatives, follow the WebView + HTML creative pattern:
```dart theme={null}
if (ad.properties.creative != null) {
webViewController.loadHtmlString(ad.properties.creative!);
}
```
Bind the WebView inside `AdropNativeAdView` so that click and impression tracking stay attached to the ad container.
If a placement absolutely requires a custom player and you accept that VTR will not be measured, you can still surface click and impression attribution by keeping the ad bound to `AdropNativeAdView`. In that case, treat the placement as **non-VTR inventory** in your internal reporting and avoid mixing it with SDK-measured video performance.
***
## Using Extra Fields
Additional fields defined by publishers can be accessed from the `extra` map.
```dart theme={null}
onAdReceived: (ad) {
// Access extra fields
final customField = ad.properties.extra['customFieldKey'];
if (customField != null) {
debugPrint('Custom field: $customField');
}
setState(() {
isLoaded = true;
});
}
```
***
## Error Handling
### Backfill-Related Errors
When backfill ads are configured, the `backfillNoFill` error code is returned when both direct ads and backfill ads are unavailable.
```dart theme={null}
onAdFailedToReceive: (ad, errorCode) {
if (errorCode == AdropErrorCode.backfillNoFill) {
debugPrint('No backfill ads available');
}
}
```
### General Error Handling
```dart theme={null}
class _NativeAdState extends State {
bool isLoaded = false;
AdropErrorCode? errorCode;
AdropNativeAd? nativeAd;
@override
void initState() {
super.initState();
nativeAd = AdropNativeAd(
unitId: 'YOUR_UNIT_ID',
listener: AdropNativeListener(
onAdReceived: (ad) {
setState(() {
isLoaded = true;
errorCode = null;
});
},
onAdFailedToReceive: (ad, error) {
// Handle backfill-related errors
if (error == AdropErrorCode.backfillNoFill) {
debugPrint('No backfill ads available');
}
setState(() {
isLoaded = false;
errorCode = error;
});
},
),
);
nativeAd?.load();
}
@override
Widget build(BuildContext context) {
if (isLoaded) {
return _buildNativeAdView();
} else if (errorCode != null) {
return Text('Ad load failed: ${errorCode?.code}');
} else {
return const SizedBox.shrink();
}
}
Widget _buildNativeAdView() {
// Native ad UI composition
return AdropNativeAdView(
ad: nativeAd,
child: Container(
// ...
),
);
}
}
```
***
## Best Practices
### 1. Recreating Ads
To load a new ad, create a new `AdropNativeAd` instance.
```dart theme={null}
void resetAd() {
setState(() {
isLoaded = false;
});
nativeAd = AdropNativeAd(
unitId: 'YOUR_UNIT_ID',
listener: AdropNativeListener(
onAdReceived: (ad) {
setState(() {
isLoaded = true;
});
},
),
);
nativeAd?.load();
}
```
### 2. Conditional Rendering
Display an appropriate placeholder until the ad loads.
```dart theme={null}
Widget buildNativeAd() {
if (isLoaded && nativeAd != null) {
return _buildNativeAdView();
} else if (isLoading) {
return const SizedBox(
height: 200,
child: Center(child: CircularProgressIndicator()),
);
} else {
return const SizedBox.shrink();
}
}
```
### 3. Responsive Layout
Configure layout to adapt to various screen sizes.
```dart theme={null}
Widget _buildNativeAdView() {
return AdropNativeAdView(
ad: nativeAd,
child: LayoutBuilder(
builder: (context, constraints) {
return Container(
width: constraints.maxWidth,
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Ad content
],
),
);
},
),
);
}
```
### 4. Null Check for Ad Properties
Native ad properties can be null, so always check them.
```dart theme={null}
Widget _buildHeadline() {
final headline = nativeAd?.properties.headline;
if (headline == null || headline.isEmpty) {
return const SizedBox.shrink();
}
return Text(
headline,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
);
}
```
***
## Next Steps
Implement full-screen interstitial ads
Implement rewarded ads that provide rewards
Implement popup-style ads
Reference types, methods, and error codes
# Popup Ads
Source: https://docs.adrop.io/sdk/flutter/popup
Learn how to implement popup ads in your Flutter app.
## Overview
Popup ads are displayed as a popup at the center or bottom of the screen. They support a "Don't show today" feature to improve user experience.
### Key Features
* Selectable center or bottom position
* "Don't show today" feature support
* Customizable button colors
* Image ad support
Use test unit IDs in development environment:
* Bottom (Image): `PUBLIC_TEST_UNIT_ID_POPUP_BOTTOM`
* Center (Image): `PUBLIC_TEST_UNIT_ID_POPUP_CENTER`
* Bottom Video 16:9: `PUBLIC_TEST_UNIT_ID_POPUP_BOTTOM_VIDEO_16_9`
* Bottom Video 9:16: `PUBLIC_TEST_UNIT_ID_POPUP_BOTTOM_VIDEO_9_16`
* Center Video 16:9: `PUBLIC_TEST_UNIT_ID_POPUP_CENTER_VIDEO_16_9`
* Center Video 9:16: `PUBLIC_TEST_UNIT_ID_POPUP_CENTER_VIDEO_9_16`
***
## AdropPopupAd
### Constructor
```dart theme={null}
AdropPopupAd({
required String unitId,
AdropPopupListener? listener,
Color? closeTextColor,
Color? hideForTodayTextColor,
Color? backgroundColor,
})
```
**Parameters**
| Parameter | Type | Required | Description |
| - | - | - | - |
| `unitId` | `String` | Y | Unit ID created in Ad Control Console |
| `listener` | `AdropPopupListener` | N | Ad event listener |
| `closeTextColor` | `Color` | N | Close button text color |
| `hideForTodayTextColor` | `Color` | N | "Don't show today" button text color |
| `backgroundColor` | `Color` | N | Button background color |
### Properties
| Property | Type | Description |
| - | - | - |
| `isLoaded` | `bool` | Whether ad is loaded |
| `unitId` | `String` | Ad unit ID |
| `creativeId` | `String` | Creative ID |
| `txId` | `String` | Transaction ID |
| `campaignId` | `String` | Campaign ID |
| `destinationURL` | `String` | Destination URL |
| `browserTarget` | `BrowserTarget?` | Browser target (external or internal) |
### Methods
| Method | Return Type | Description |
| - | - | - |
| `load()` | `Future` | Loads the ad |
| `show()` | `Future` | Shows the ad on screen |
| `close()` | `Future` | Closes the ad |
| `dispose()` | `Future` | Releases resources |
***
## Basic Usage
```dart theme={null}
import 'package:flutter/material.dart';
import 'package:adrop_ads_flutter/adrop_ads_flutter.dart';
class PopupExample extends StatefulWidget {
const PopupExample({super.key});
@override
State createState() => _PopupExampleState();
}
class _PopupExampleState extends State {
bool isLoaded = false;
AdropPopupAd? popupAd;
@override
void initState() {
super.initState();
_createPopupAd();
}
void _createPopupAd() {
popupAd = AdropPopupAd(
unitId: 'YOUR_UNIT_ID',
listener: AdropPopupListener(
onAdReceived: (ad) {
debugPrint('Popup ad loaded successfully: ${ad.creativeId}');
setState(() {
isLoaded = true;
});
},
onAdFailedToReceive: (ad, errorCode) {
debugPrint('Popup ad failed to load: $errorCode');
},
onAdClicked: (ad) {
debugPrint('Popup ad clicked');
},
onAdImpression: (ad) {
debugPrint('Popup ad impression');
},
onAdDidPresentFullScreen: (ad) {
debugPrint('Popup ad presented');
},
onAdDidDismissFullScreen: (ad) {
debugPrint('Popup ad dismissed');
},
onAdFailedToShowFullScreen: (ad, errorCode) {
debugPrint('Popup ad failed to show: $errorCode');
},
),
);
}
void loadAd() {
popupAd?.load();
}
void showAd() {
if (isLoaded) {
popupAd?.show();
}
}
@override
void dispose() {
popupAd?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Popup Ad Example')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: loadAd,
child: const Text('Load Ad'),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: isLoaded ? showAd : null,
child: const Text('Show Ad'),
),
],
),
),
);
}
}
```
***
## AdropPopupListener
Listener for handling popup ad events.
### Callbacks
```dart theme={null}
AdropPopupListener(
onAdReceived: (AdropAd ad) {
// Ad loaded successfully
},
onAdClicked: (AdropAd ad) {
// Ad clicked
},
onAdImpression: (AdropAd ad) {
// Ad impression
},
onAdWillPresentFullScreen: (AdropAd ad) {
// Just before ad is presented (iOS only)
},
onAdDidPresentFullScreen: (AdropAd ad) {
// Ad presented
},
onAdWillDismissFullScreen: (AdropAd ad) {
// Just before ad is dismissed (iOS only)
},
onAdDidDismissFullScreen: (AdropAd ad) {
// Ad dismissed
},
onAdFailedToReceive: (AdropAd ad, AdropErrorCode errorCode) {
// Ad failed to load
},
onAdFailedToShowFullScreen: (AdropAd ad, AdropErrorCode errorCode) {
// Ad failed to show
},
onAdVideoStart: (AdropAd ad) {
// Video ad started playing
},
onAdVideoEnd: (AdropAd ad) {
// Video ad finished playing
},
)
```
### Callback Descriptions
| Callback | Description |
| - | - |
| `onAdReceived` | Called when ad loads successfully |
| `onAdClicked` | Called when ad is clicked |
| `onAdImpression` | Called when ad is shown |
| `onAdWillPresentFullScreen` | Called just before ad is presented (iOS only) |
| `onAdDidPresentFullScreen` | Called after ad is presented |
| `onAdWillDismissFullScreen` | Called just before ad is dismissed (iOS only) |
| `onAdDidDismissFullScreen` | Called after ad is dismissed |
| `onAdFailedToReceive` | Called when ad fails to load |
| `onAdFailedToShowFullScreen` | Called when ad fails to show |
| `onAdVideoStart` | Called when video ad starts playing |
| `onAdVideoEnd` | Called when video ad finishes playing |
***
## Button Color Customization
You can customize the colors of the close button and "Don't show today" button.
```dart theme={null}
popupAd = AdropPopupAd(
unitId: 'YOUR_UNIT_ID',
// Close button text color
closeTextColor: Colors.white,
// "Don't show today" button text color
hideForTodayTextColor: Colors.white,
// Button background color
backgroundColor: Colors.black87,
listener: AdropPopupListener(
onAdReceived: (ad) {
setState(() {
isLoaded = true;
});
},
),
);
```
### Color Options
| Parameter | Description | Default |
| - | - | - |
| `closeTextColor` | Close button text color | System default |
| `hideForTodayTextColor` | "Don't show today" button text color | System default |
| `backgroundColor` | Button background color | System default |
***
## Popup Position
The popup position is configured when creating the unit in the Ad Control Console.
### Center Popup
The popup is displayed at the center of the screen. Suitable for announcements and event notifications.
```dart theme={null}
// Use center popup unit ID
popupAd = AdropPopupAd(
unitId: 'YOUR_CENTER_POPUP_UNIT_ID',
listener: AdropPopupListener(...),
);
```
### Bottom Popup
The popup is displayed at the bottom of the screen. Suitable for banner-style notifications on app launch.
```dart theme={null}
// Use bottom popup unit ID
popupAd = AdropPopupAd(
unitId: 'YOUR_BOTTOM_POPUP_UNIT_ID',
listener: AdropPopupListener(...),
);
```
***
## Don't Show Today
When a user selects "Don't show today", the ad will not be shown on that device until midnight.
When loading an ad in "Don't show today" state, the `adHideForToday` error is returned.
```dart theme={null}
onAdFailedToReceive: (ad, errorCode) {
if (errorCode == AdropErrorCode.adHideForToday) {
debugPrint('User selected don\'t show today.');
// Display alternative content instead of ad
}
}
```
***
## Ad Recreation
Popup ads are one-time use. Once shown, they cannot be shown again, so you must recreate the instance to load a new ad.
```dart theme={null}
class _PopupState extends State {
bool isLoaded = false;
bool isShown = false;
AdropPopupAd? popupAd;
@override
void initState() {
super.initState();
_createPopupAd();
}
void _createPopupAd() {
popupAd?.dispose();
popupAd = AdropPopupAd(
unitId: 'YOUR_UNIT_ID',
backgroundColor: Colors.black87,
listener: AdropPopupListener(
onAdReceived: (ad) {
setState(() {
isLoaded = true;
});
},
onAdDidDismissFullScreen: (ad) {
setState(() {
isShown = true;
});
// Prepare new ad for next show
_createPopupAd();
popupAd?.load();
},
),
);
setState(() {
isLoaded = false;
isShown = false;
});
}
@override
void dispose() {
popupAd?.dispose();
super.dispose();
}
}
```
***
## Error Handling
```dart theme={null}
class _PopupState extends State {
bool isLoaded = false;
AdropErrorCode? errorCode;
AdropPopupAd? popupAd;
void _createPopupAd() {
popupAd = AdropPopupAd(
unitId: 'YOUR_UNIT_ID',
listener: AdropPopupListener(
onAdReceived: (ad) {
setState(() {
isLoaded = true;
errorCode = null;
});
},
onAdFailedToReceive: (ad, error) {
setState(() {
isLoaded = false;
errorCode = error;
});
_handleError(error);
},
onAdFailedToShowFullScreen: (ad, error) {
setState(() {
errorCode = error;
});
_handleError(error);
},
),
);
}
void _handleError(AdropErrorCode error) {
switch (error) {
case AdropErrorCode.network:
debugPrint('Network error occurred.');
break;
case AdropErrorCode.adNoFill:
debugPrint('No ads available.');
break;
case AdropErrorCode.adHideForToday:
debugPrint('User selected don\'t show today.');
break;
case AdropErrorCode.adShown:
debugPrint('Ad already shown.');
break;
default:
debugPrint('Error occurred: ${error.code}');
}
}
@override
Widget build(BuildContext context) {
return Column(
children: [
ElevatedButton(
onPressed: popupAd?.load,
child: const Text('Load Ad'),
),
ElevatedButton(
onPressed: isLoaded ? popupAd?.show : null,
child: const Text('Show Ad'),
),
if (errorCode != null)
Text(
'Error: ${errorCode?.code}',
style: const TextStyle(color: Colors.red),
),
],
);
}
}
```
***
## Best Practices
### 1. Show Popup on App Launch
Display announcements or events as a popup when the app launches.
```dart theme={null}
class SplashScreen extends StatefulWidget {
@override
State createState() => _SplashScreenState();
}
class _SplashScreenState extends State {
AdropPopupAd? popupAd;
@override
void initState() {
super.initState();
_loadPopupAd();
}
void _loadPopupAd() {
popupAd = AdropPopupAd(
unitId: 'YOUR_UNIT_ID',
listener: AdropPopupListener(
onAdReceived: (ad) {
// Show ad when loaded
popupAd?.show();
},
onAdFailedToReceive: (ad, errorCode) {
// Navigate to main screen if ad load fails
if (errorCode != AdropErrorCode.adHideForToday) {
_navigateToMain();
}
},
onAdDidDismissFullScreen: (ad) {
// Navigate to main screen after popup is dismissed
_navigateToMain();
},
),
);
popupAd?.load();
}
void _navigateToMain() {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (_) => const MainScreen()),
);
}
@override
void dispose() {
popupAd?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
}
```
### 2. Use Theme-Matched Colors
Set button colors to match your app's theme.
```dart theme={null}
void _createPopupAd() {
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
popupAd = AdropPopupAd(
unitId: 'YOUR_UNIT_ID',
closeTextColor: isDarkMode ? Colors.white : Colors.black,
hideForTodayTextColor: isDarkMode ? Colors.white70 : Colors.black54,
backgroundColor: isDarkMode ? Colors.grey[800] : Colors.grey[200],
listener: AdropPopupListener(...),
);
}
```
### 3. Resource Management
Always dispose of unused ad instances.
```dart theme={null}
@override
void dispose() {
popupAd?.dispose();
super.dispose();
}
```
***
## Complete Example
```dart theme={null}
import 'package:flutter/material.dart';
import 'package:adrop_ads_flutter/adrop_ads_flutter.dart';
class PopupAdPage extends StatefulWidget {
const PopupAdPage({super.key});
@override
State createState() => _PopupAdPageState();
}
class _PopupAdPageState extends State {
bool isLoaded = false;
bool isLoading = false;
bool isShown = false;
AdropErrorCode? errorCode;
AdropPopupAd? popupAd;
@override
void initState() {
super.initState();
_createPopupAd();
}
void _createPopupAd() {
popupAd?.dispose();
popupAd = AdropPopupAd(
unitId: 'PUBLIC_TEST_UNIT_ID_POPUP_BOTTOM', // Test unit ID
closeTextColor: Colors.white,
hideForTodayTextColor: Colors.white70,
backgroundColor: Colors.black87,
listener: AdropPopupListener(
onAdReceived: (ad) {
debugPrint('Ad loaded successfully');
debugPrint('Creative ID: ${ad.creativeId}');
debugPrint('Campaign ID: ${ad.campaignId}');
setState(() {
isLoaded = true;
isLoading = false;
errorCode = null;
});
},
onAdClicked: (ad) {
debugPrint('Ad clicked: ${ad.destinationURL}');
},
onAdImpression: (ad) {
debugPrint('Ad impression: ${ad.creativeId}');
},
onAdWillPresentFullScreen: (ad) {
debugPrint('Ad will present');
},
onAdDidPresentFullScreen: (ad) {
debugPrint('Ad presented');
},
onAdWillDismissFullScreen: (ad) {
debugPrint('Ad will dismiss');
},
onAdDidDismissFullScreen: (ad) {
debugPrint('Ad dismissed');
setState(() {
isShown = true;
});
},
onAdFailedToReceive: (ad, error) {
debugPrint('Ad failed to load: $error');
setState(() {
isLoaded = false;
isLoading = false;
errorCode = error;
});
},
onAdFailedToShowFullScreen: (ad, error) {
debugPrint('Ad failed to show: $error');
setState(() {
errorCode = error;
});
},
),
);
setState(() {
isLoaded = false;
isShown = false;
errorCode = null;
});
}
void loadAd() {
setState(() {
isLoading = true;
errorCode = null;
});
popupAd?.load();
}
void showAd() {
if (isLoaded) {
popupAd?.show();
}
}
@override
void dispose() {
popupAd?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Popup Ad Example'),
),
body: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Load button
ElevatedButton(
onPressed: isLoading ? null : loadAd,
child: Text(isLoading ? 'Loading...' : 'Load Ad'),
),
const SizedBox(height: 16),
// Show button
ElevatedButton(
onPressed: isLoaded ? showAd : null,
child: const Text('Show Ad'),
),
const SizedBox(height: 16),
// Reset button
TextButton(
onPressed: (isShown || errorCode != null)
? () => _createPopupAd()
: null,
child: const Text('Reset'),
),
const SizedBox(height: 24),
// Status display
Text('Loaded: ${isLoaded ? "Yes" : "No"}'),
Text('Shown: ${isShown ? "Yes" : "No"}'),
// Error display
if (errorCode != null) ...[
const SizedBox(height: 16),
Text(
'Error: ${errorCode?.code}',
style: const TextStyle(color: Colors.red),
),
if (errorCode == AdropErrorCode.adHideForToday)
const Text(
'(User selected don\'t show today)',
style: TextStyle(color: Colors.grey, fontSize: 12),
),
],
],
),
),
),
);
}
}
```
***
## Backfill Ads
When backfill ads are enabled, backfill ads are automatically loaded when direct ads are unavailable. You can handle backfill-related errors using the error code.
```dart theme={null}
popupAd = AdropPopupAd(
unitId: 'YOUR_UNIT_ID',
listener: AdropPopupListener(
onAdReceived: (ad) {
debugPrint('Ad loaded: ${ad.creativeId}');
},
onAdFailedToReceive: (ad, errorCode) {
switch (errorCode) {
case AdropErrorCode.adNoFill:
debugPrint('No direct ad available');
break;
case AdropErrorCode.backfillNoFill:
debugPrint('No backfill ad available either');
break;
default:
debugPrint('Ad load failed: ${errorCode.code}');
}
},
),
);
```
To use backfill ads, add the backfill dependency to native platforms. See [Getting Started](/sdk/flutter/overview).
***
## Next Steps
Set up targeting for personalized ads
Reference types, methods, and error codes
Explore various implementation examples
# Reference
Source: https://docs.adrop.io/sdk/flutter/reference
Detailed reference for all classes and enums in the Adrop Flutter SDK.
## Adrop
The main class for the Adrop SDK. Handles SDK initialization and global settings.
### Static Properties
| Property | Type | Description |
| - | - | - |
| `consentManager` | `AdropConsentManager` | Consent manager for GDPR/CCPA consent management |
### Static Methods
| Method | Description |
| - | - |
| `initialize(bool production, {List? targetCountries, bool? useInAppBrowser})` | Initialize the SDK |
| `setUID(String uid)` | Set user identifier (hashed with SHA-256) |
| `setTheme(AdropTheme theme)` | Set ad theme |
| `setMarketingConsent(bool consent)` | Set the user's marketing consent for push notification ad targeting. Call after `initialize()`. |
| `registerWebView(int webViewIdentifier)` | Register a WebView for backfill ad support |
### Usage Example
```dart theme={null}
import 'package:adrop_ads_flutter/adrop_ads_flutter.dart';
// Basic initialization (test mode)
await Adrop.initialize(false);
// Production mode + target country setting
await Adrop.initialize(
true,
targetCountries: ['KR', 'JP', 'US'],
);
// Set user ID
await Adrop.setUID('user123');
// Set theme
await Adrop.setTheme(AdropTheme.dark);
```
```dart theme={null}
// Register WebView
import 'dart:io';
import 'package:webview_flutter_android/webview_flutter_android.dart';
import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart';
final int identifier;
if (Platform.isAndroid) {
identifier = (controller.platform as AndroidWebViewController).webViewIdentifier;
} else if (Platform.isIOS) {
identifier = (controller.platform as WebKitWebViewController).webViewIdentifier;
}
await Adrop.registerWebView(identifier);
```
***
## AdropTheme
Enum for dark mode settings for ads.
### Values
| Value | Description |
| - | - |
| `light` | Light mode |
| `dark` | Dark mode |
| `auto` | Auto-follow system settings (default) |
### Usage Example
```dart theme={null}
await Adrop.setTheme(AdropTheme.dark);
```
***
## AdropErrorCode
Error codes that can occur when loading and displaying ads.
### Values
| Error Code | Code String | Description |
| - | - | - |
| `network` | `ERROR_CODE_NETWORK` | Network error |
| `internal` | `ERROR_CODE_INTERNAL` | Internal error |
| `initialize` | `ERROR_CODE_INITIALIZE` | SDK initialization error |
| `invalidUnit` | `ERROR_CODE_INVALID_UNIT` | Invalid ad unit ID |
| `notTargetCountry` | `ERROR_CODE_NOT_TARGET_COUNTRY` | Not a target country |
| `inactive` | `ERROR_CODE_AD_INACTIVE` | Deactivated ad |
| `adNoFill` | `ERROR_CODE_AD_NO_FILL` | No available ads |
| `adLoadDuplicate` | `ERROR_CODE_AD_LOAD_DUPLICATED` | Duplicate ad load request |
| `adLoading` | `ERROR_CODE_AD_LOADING` | Ad loading in progress |
| `adEmpty` | `ERROR_CODE_AD_EMPTY` | Ad is empty |
| `adShown` | `ERROR_CODE_AD_SHOWN` | Ad already shown |
| `adHideForToday` | `ERROR_CODE_AD_HIDE_FOR_TODAY` | Hide for today enabled |
| `accountUsageLimitExceeded` | `ERROR_CODE_ACCOUNT_USAGE_LIMIT_EXCEEDED` | Account usage limit exceeded |
| `adLandscapeUnsupported` | `ERROR_CODE_LANDSCAPE_UNSUPPORTED` | Landscape mode not supported |
| `backfillNoFill` | `ERROR_CODE_AD_BACKFILL_NO_FILL` | No backfill ads available |
| `undefined` | `UNDEFINED` | Undefined error |
### Properties
| Property | Type | Description |
| - | - | - |
| `code` | `String` | Error code string |
### Factory Methods
| Method | Description |
| - | - |
| `AdropErrorCode.getByCode(String code)` | Retrieve error code by code string |
***
## AdropAdChoicesPosition
Enum for setting the AdChoices badge position on backfill native ads.
### Values
| Value | Integer | Description |
| - | - | - |
| `topLeft` | 0 | Top-left corner |
| `topRight` | 1 | Top-right corner (default) |
| `bottomLeft` | 2 | Bottom-left corner |
| `bottomRight` | 3 | Bottom-right corner |
### Usage Example
```dart theme={null}
final nativeAd = AdropNativeAd(
unitId: 'YOUR_UNIT_ID',
preferredAdChoicesPosition: AdropAdChoicesPosition.bottomRight,
);
```
***
## AdropBannerView
Widget class for displaying banner ads.
### Constructor
```dart theme={null}
AdropBannerView({
required String unitId,
AdropBannerListener? listener,
})
```
### Properties
| Property | Type | Description |
| - | - | - |
| `creativeSize` | `CreativeSize?` | Creative size |
| `adSize` | `Size?` | Banner view size setting |
### Methods
| Method | Return Type | Description |
| - | - | - |
| `load()` | `Future` | Load ad |
| `play()` | `Future` | Start or resume video banner playback |
| `pause()` | `Future` | Pause video banner playback |
| `dispose()` | `Future` | Release resources |
### Usage Example
```dart theme={null}
late AdropBannerView bannerView;
@override
void initState() {
super.initState();
bannerView = AdropBannerView(
unitId: 'YOUR_UNIT_ID',
listener: AdropBannerListener(
onAdReceived: (unitId, metadata) {
setState(() => isLoaded = true);
},
onAdFailedToReceive: (unitId, errorCode) {
debugPrint('Error: ${errorCode.code}');
},
),
);
bannerView.load();
}
@override
void dispose() {
bannerView.dispose();
super.dispose();
}
```
***
## AdropBannerListener
Listener class for handling banner ad events.
### Constructor
```dart theme={null}
AdropBannerListener({
void Function(String unitId, Map? metadata)? onAdReceived,
void Function(String unitId, Map? metadata)? onAdClicked,
void Function(String unitId, Map? metadata)? onAdImpression,
void Function(String unitId, AdropErrorCode errorCode)? onAdFailedToReceive,
void Function(String unitId)? onAdVideoStart,
void Function(String unitId)? onAdVideoEnd,
})
```
### Callbacks
| Callback | Parameters | Description |
| - | - | - |
| `onAdReceived` | `unitId`, `metadata` | Ad received successfully |
| `onAdClicked` | `unitId`, `metadata` | Ad clicked |
| `onAdImpression` | `unitId`, `metadata` | Ad impression |
| `onAdFailedToReceive` | `unitId`, `errorCode` | Ad failed to receive |
| `onAdVideoStart` | `unitId` | Video ad started playing |
| `onAdVideoEnd` | `unitId` | Video ad finished playing |
### Metadata Fields
| Field | Type | Description |
| - | - | - |
| `creativeId` | `String` | Creative ID |
| `txId` | `String` | Transaction ID |
| `campaignId` | `String` | Campaign ID |
| `destinationURL` | `String` | Destination URL |
| `browserTarget` | `int?` | Browser target (0: external, 1: internal) |
***
## AdropNativeAd
Class for requesting native ads.
### Constructor
```dart theme={null}
AdropNativeAd({
required String unitId,
bool useCustomClick = false,
AdropAdChoicesPosition preferredAdChoicesPosition = AdropAdChoicesPosition.topRight,
AdropNativeListener? listener,
})
```
### Properties
| Property | Type | Description |
| - | - | - |
| `isLoaded` | `bool` | Whether ad is loaded |
| `unitId` | `String` | Ad unit ID |
| `creativeId` | `String` | Creative ID |
| `txId` | `String` | Transaction ID |
| `campaignId` | `String` | Campaign ID |
| `destinationURL` | `String` | Destination URL |
| `browserTarget` | `BrowserTarget?` | How the destination URL opens (external/internal browser) |
| `properties` | `AdropNativeProperties` | Native ad properties |
| `creativeSize` | `CreativeSize` | Creative size |
| `creativeType` | `String` | Loaded creative type (`"display"` or `"video"`). Returns `"display"` until an ad response is received. |
| `isBackfilled` | `bool` | Whether it is a backfilled ad |
| `preferredAdChoicesPosition` | `AdropAdChoicesPosition` | AdChoices badge position for backfill native ads (default: `topRight`) |
### Methods
| Method | Return Type | Description |
| - | - | - |
| `load()` | `Future` | Load ad |
### Usage Example
```dart theme={null}
final nativeAd = AdropNativeAd(
unitId: 'YOUR_UNIT_ID',
useCustomClick: true,
listener: AdropNativeListener(
onAdReceived: (ad) {
debugPrint('Ad received: ${ad.creativeId}');
},
onAdFailedToReceive: (ad, errorCode) {
debugPrint('Error: ${errorCode.code}');
},
),
);
await nativeAd.load();
```
***
## AdropNativeAdView
Widget for displaying native ads on screen.
### Constructor
```dart theme={null}
AdropNativeAdView({
required AdropNativeAd? ad,
required Widget child,
})
```
### Usage Example
```dart theme={null}
AdropNativeAdView(
ad: nativeAd,
child: Container(
child: Column(
children: [
Text(nativeAd?.properties.headline ?? ''),
Text(nativeAd?.properties.body ?? ''),
],
),
),
)
```
***
## AdropNativeListener
Listener class for handling native ad events.
### Constructor
```dart theme={null}
AdropNativeListener({
void Function(AdropNativeAd ad)? onAdReceived,
void Function(AdropNativeAd ad)? onAdClicked,
void Function(AdropNativeAd ad)? onAdImpression,
void Function(AdropNativeAd ad, AdropErrorCode errorCode)? onAdFailedToReceive,
void Function(AdropNativeAd ad)? onAdVideoStart,
void Function(AdropNativeAd ad)? onAdVideoEnd,
})
```
### Callbacks
| Callback | Parameters | Description |
| - | - | - |
| `onAdReceived` | `ad` | Ad received successfully |
| `onAdClicked` | `ad` | Ad clicked |
| `onAdImpression` | `ad` | Ad impression |
| `onAdFailedToReceive` | `ad`, `errorCode` | Ad failed to receive |
| `onAdVideoStart` | `ad` | Video ad started playing |
| `onAdVideoEnd` | `ad` | Video ad finished playing |
***
## AdropNativeProperties
Content properties for native ads.
### Properties
| Property | Type | Description |
| - | - | - |
| `headline` | `String?` | Ad headline |
| `body` | `String?` | Ad body |
| `creative` | `String?` | HTML creative content |
| `asset` | `String?` | Image asset URL |
| `destinationURL` | `String?` | URL to navigate on click |
| `callToAction` | `String?` | CTA button text |
| `profile` | `AdropNativeProfile?` | Advertiser profile info |
| `extra` | `Map` | Additional custom fields |
| `isBackfilled` | `bool` | Whether it is a backfilled ad |
***
## AdropNativeProfile
Profile information for native ads.
### Properties
| Property | Type | Description |
| - | - | - |
| `displayName` | `String?` | Advertiser name |
| `displayLogo` | `String?` | Advertiser logo image URL |
***
## AdropInterstitialAd
Class for displaying interstitial ads.
### Constructor
```dart theme={null}
AdropInterstitialAd({
required String unitId,
AdropInterstitialListener? listener,
})
```
### Properties
| Property | Type | Description |
| - | - | - |
| `isLoaded` | `bool` | Whether ad is loaded |
| `unitId` | `String` | Ad unit ID |
| `creativeId` | `String` | Creative ID |
| `creativeType` | `String` | Loaded creative type (`"display"` or `"video"`). Returns `"display"` until an ad response is received. |
| `txId` | `String` | Transaction ID |
| `campaignId` | `String` | Campaign ID |
| `destinationURL` | `String` | Destination URL |
| `browserTarget` | `BrowserTarget?` | How the destination URL opens (external/internal browser) |
### Methods
| Method | Return Type | Description |
| - | - | - |
| `load()` | `Future` | Load ad |
| `show()` | `Future` | Show ad |
| `close()` | `Future` | Programmatically close the ad (Android only) |
| `dispose()` | `Future` | Release resources |
### Usage Example
```dart theme={null}
final interstitialAd = AdropInterstitialAd(
unitId: 'YOUR_UNIT_ID',
listener: AdropInterstitialListener(
onAdReceived: (ad) {
ad.show();
},
onAdFailedToReceive: (ad, errorCode) {
debugPrint('Error: ${errorCode.code}');
},
onAdDidDismissFullScreen: (ad) {
debugPrint('Ad dismissed');
},
),
);
await interstitialAd.load();
```
***
## AdropInterstitialListener
Listener class for handling interstitial ad events.
### Constructor
```dart theme={null}
AdropInterstitialListener({
void Function(AdropAd ad)? onAdReceived,
void Function(AdropAd ad)? onAdClicked,
void Function(AdropAd ad)? onAdImpression,
void Function(AdropAd ad)? onAdWillPresentFullScreen,
void Function(AdropAd ad)? onAdDidPresentFullScreen,
void Function(AdropAd ad)? onAdWillDismissFullScreen,
void Function(AdropAd ad)? onAdDidDismissFullScreen,
void Function(AdropAd ad, AdropErrorCode errorCode)? onAdFailedToReceive,
void Function(AdropAd ad, AdropErrorCode errorCode)? onAdFailedToShowFullScreen,
void Function(AdropAd ad)? onAdBackButtonPressed,
})
```
### Callbacks
| Callback | Description |
| - | - |
| `onAdReceived` | Ad received successfully |
| `onAdClicked` | Ad clicked |
| `onAdImpression` | Ad impression |
| `onAdWillPresentFullScreen` | Ad will present (iOS only) |
| `onAdDidPresentFullScreen` | Ad presented |
| `onAdWillDismissFullScreen` | Ad will dismiss (iOS only) |
| `onAdDidDismissFullScreen` | Ad dismissed |
| `onAdFailedToReceive` | Ad failed to receive |
| `onAdFailedToShowFullScreen` | Ad failed to show |
| `onAdBackButtonPressed` | Back button pressed while ad is displayed (Android only) |
***
## AdropRewardedAd
Class for displaying rewarded ads.
### Constructor
```dart theme={null}
AdropRewardedAd({
required String unitId,
AdropRewardedListener? listener,
})
```
### Properties
| Property | Type | Description |
| - | - | - |
| `isLoaded` | `bool` | Whether ad is loaded |
| `unitId` | `String` | Ad unit ID |
| `creativeId` | `String` | Creative ID |
| `creativeType` | `String` | Loaded creative type (`"display"` or `"video"`). Returns `"display"` until an ad response is received. |
| `txId` | `String` | Transaction ID |
| `campaignId` | `String` | Campaign ID |
| `destinationURL` | `String` | Destination URL |
| `browserTarget` | `BrowserTarget?` | How the destination URL opens (external/internal browser) |
| `serverSideVerificationOptions` | `ServerSideVerificationOptions?` | Server-side verification options (userId, customData) |
### Methods
| Method | Return Type | Description |
| - | - | - |
| `load()` | `Future` | Load ad |
| `show()` | `Future` | Show ad |
| `dispose()` | `Future` | Release resources |
### Usage Example
```dart theme={null}
final rewardedAd = AdropRewardedAd(
unitId: 'YOUR_UNIT_ID',
listener: AdropRewardedListener(
onAdReceived: (ad) {
ad.show();
},
onAdEarnRewardHandler: (ad, type, amount) {
// Grant reward
debugPrint('Reward: type=$type, amount=$amount');
},
),
);
await rewardedAd.load();
```
***
## ServerSideVerificationOptions
Options for server-side verification (SSV) in rewarded ads. Set before calling `load()` to pass custom data to your server callback.
### Constructor
```dart theme={null}
const ServerSideVerificationOptions({
String? userId,
String? customData,
})
```
### Properties
| Property | Type | Description |
| - | - | - |
| `userId` | `String?` | User identifier for server-side verification |
| `customData` | `String?` | Custom data string passed to the server callback |
### Usage Example
```dart theme={null}
final rewardedAd = AdropRewardedAd(
unitId: 'YOUR_UNIT_ID',
listener: AdropRewardedListener(
onAdEarnRewardHandler: (ad, type, amount) {
debugPrint('Reward: type=$type, amount=$amount');
},
),
);
// Set SSV options before loading
rewardedAd.serverSideVerificationOptions = ServerSideVerificationOptions(
userId: 'user_123',
customData: 'extra_data',
);
await rewardedAd.load();
```
***
## AdropRewardedListener
Listener class for handling rewarded ad events.
### Constructor
```dart theme={null}
AdropRewardedListener({
void Function(AdropAd ad)? onAdReceived,
void Function(AdropAd ad)? onAdClicked,
void Function(AdropAd ad)? onAdImpression,
void Function(AdropAd ad)? onAdWillPresentFullScreen,
void Function(AdropAd ad)? onAdDidPresentFullScreen,
void Function(AdropAd ad)? onAdWillDismissFullScreen,
void Function(AdropAd ad)? onAdDidDismissFullScreen,
void Function(AdropAd ad, AdropErrorCode errorCode)? onAdFailedToReceive,
void Function(AdropAd ad, AdropErrorCode errorCode)? onAdFailedToShowFullScreen,
void Function(AdropAd ad, int type, int amount)? onAdEarnRewardHandler,
})
```
### Callbacks
| Callback | Description |
| - | - |
| `onAdReceived` | Ad received successfully |
| `onAdClicked` | Ad clicked |
| `onAdImpression` | Ad impression |
| `onAdWillPresentFullScreen` | Ad will present (iOS only) |
| `onAdDidPresentFullScreen` | Ad presented |
| `onAdWillDismissFullScreen` | Ad will dismiss (iOS only) |
| `onAdDidDismissFullScreen` | Ad dismissed |
| `onAdFailedToReceive` | Ad failed to receive |
| `onAdFailedToShowFullScreen` | Ad failed to show |
| `onAdEarnRewardHandler` | Reward earned |
***
## AdropPopupAd
Class for displaying popup ads.
### Constructor
```dart theme={null}
AdropPopupAd({
required String unitId,
AdropPopupListener? listener,
Color? closeTextColor,
Color? hideForTodayTextColor,
Color? backgroundColor,
})
```
### Properties
| Property | Type | Description |
| - | - | - |
| `isLoaded` | `bool` | Whether ad is loaded |
| `unitId` | `String` | Ad unit ID |
| `creativeId` | `String` | Creative ID |
| `creativeType` | `String` | Loaded creative type (`"display"` or `"video"`). Returns `"display"` until an ad response is received. |
| `txId` | `String` | Transaction ID |
| `campaignId` | `String` | Campaign ID |
| `destinationURL` | `String` | Destination URL |
| `browserTarget` | `BrowserTarget?` | How the destination URL opens (external/internal browser) |
| `closeTextColor` | `Color?` | Close button text color |
| `hideForTodayTextColor` | `Color?` | "Hide for today" text color |
| `backgroundColor` | `Color?` | Button background color |
### Methods
| Method | Return Type | Description |
| - | - | - |
| `load()` | `Future` | Load ad |
| `show()` | `Future` | Show ad |
| `close()` | `Future` | Close ad |
| `dispose()` | `Future` | Release resources |
### Usage Example
```dart theme={null}
final popupAd = AdropPopupAd(
unitId: 'YOUR_UNIT_ID',
closeTextColor: Colors.white,
backgroundColor: Colors.black87,
listener: AdropPopupListener(
onAdReceived: (ad) {
ad.show();
},
),
);
await popupAd.load();
```
***
## AdropPopupListener
Listener class for handling popup ad events.
### Constructor
```dart theme={null}
AdropPopupListener({
void Function(AdropAd ad)? onAdReceived,
void Function(AdropAd ad)? onAdClicked,
void Function(AdropAd ad)? onAdImpression,
void Function(AdropAd ad)? onAdWillPresentFullScreen,
void Function(AdropAd ad)? onAdDidPresentFullScreen,
void Function(AdropAd ad)? onAdWillDismissFullScreen,
void Function(AdropAd ad)? onAdDidDismissFullScreen,
void Function(AdropAd ad, AdropErrorCode errorCode)? onAdFailedToReceive,
void Function(AdropAd ad, AdropErrorCode errorCode)? onAdFailedToShowFullScreen,
void Function(AdropAd ad)? onAdVideoStart,
void Function(AdropAd ad)? onAdVideoEnd,
})
```
***
## AdropMetrics
Class for managing user properties and events.
### Static Methods
| Method | Return Type | Description |
| - | - | - |
| `setProperty(String key, dynamic value)` | `Future` | Set user property |
| `sendEvent(String name, [Map? params])` | `Future` | Send event |
| `logEvent(String name, [dynamic params])` | `Future` | Log event (deprecated, use `sendEvent`) |
| `properties()` | `Future