Sellwild SDK -- React Native Integration Guide
Server-side header bidding for React Native applications, powered by Prebid Server.
Table of Contents
- Prerequisites
- Installation
- Native Feed (1.3.5+) ⭐ Recommended
- Native Banner (1.3.0+)
- iOS Configuration
- Android Configuration
- Basic Integration
- Native Listing Cards
- Direct Prebid Server Auction
- Prebid Server Configuration
- Metro Configuration
- GDPR and Privacy
- TypeScript Reference
- Troubleshooting
- Migration Guide: Widget → Feed
Prerequisites
| Requirement | Minimum Version | Notes |
|---|---|---|
| Node.js | 18.0+ | LTS recommended |
| React Native | 0.72+ | New Architecture (Fabric) supported on 0.74+ |
| Xcode | 15.0+ | Required for iOS builds; macOS Sonoma or later |
| Android Studio | Hedgehog (2023.1.1)+ | JDK 17 bundled |
| CocoaPods | 1.14+ | sudo gem install cocoapods if not installed |
react-native-webview | 11.0+ | Peer dependency. Required only if you use <SellwildWidget> (the marketplace listings surface). The native banner path does not depend on it. |
Verify your environment before proceeding:
npx react-native doctorInstallation
1. Install packages
npm install @sellwild/react-native-sdkOr with Yarn:
yarn add @sellwild/react-native-sdkreact-native-webview is declared as a peer dependency. It is only required if your integration uses <SellwildWidget> (the marketplace listings surface). If you do, install it alongside the SDK:
npm install react-native-webview
# or
yarn add react-native-webview2. iOS -- Install native dependencies
cd ios && pod install && cd ..If you are using the New Architecture, ensure your Podfile includes:
ENV['RCT_NEW_ARCH_ENABLED'] = '1'3. Android -- Auto-linking
No additional steps are required. React Native auto-linking resolves the Sellwild RN bridge automatically during the Gradle build.
Confirm auto-linking registered the package:
npx react-native configThe output should list @sellwild/react-native-sdk under dependencies.
Native Feed (1.3.5+)
⭐ Recommended for marketplace listings. Higher CPMs than the WebView widget, native scrolling performance, and reliable tap handling.
<SellwildFeed> is an all-in-one native surface: a single-column scroll of native listing cards interleaved with native Prebid + GAM ads. There is no WebView — every row is native on both iOS (UITableView) and Android (RecyclerView).
import React, { useEffect, useState } from 'react';
import { SafeAreaView, ActivityIndicator } from 'react-native';
import { SellwildFeed, configure } from '@sellwild/react-native-sdk';
export default function MarketplaceScreen() {
const [config, setConfig] = useState(null);
useEffect(() => {
configure('weatherbug', 'weatherbug-weatherbug').then(setConfig);
}, []);
if (!config) return <ActivityIndicator />;
return (
<SafeAreaView style={{ flex: 1 }}>
<SellwildFeed
config={config}
style={{ flex: 1 }}
onLoad={() => console.log('Feed loaded')}
onListingTap={(listing) => {
console.log('Tapped:', listing.title);
return false; // Let SDK open in browser
}}
onAdImpression={(zoneId) => console.log('Ad impression:', zoneId)}
onError={(err) => console.warn('Feed error:', err.message)}
/>
</SafeAreaView>
);
}Props
| Prop | Type | Description |
|---|---|---|
config | SellwildConfig | Resolved config from configure(partnerCode, slug). |
style | ViewStyle? | Optional style override. Feed expands to fill container by default. |
Events
| Event | Signature | Fires when |
|---|---|---|
onLoad | () => void | Initial listings fetch completes successfully. |
onListingTap | (listing: SellwildListing) => boolean | void | User taps a listing card. Return true to consume (handle yourself); return false/omit to let SDK open the URL in-app browser. |
onAdImpression | (zoneId: string) => void | A native ad row records an impression. |
onAdClicked | (zoneId: string) => void | A native ad row is clicked. |
onError | (error: Error) => void | Listings fetch fails or a row fails to render. |
Migrating from SellwildWidget
If you're currently using <SellwildWidget> (the WebView-based component), see the Migration Guide for step-by-step instructions.
Native banner path (1.3.0+)
As of 1.3.0, <SellwildBanner> is backed by a native iOS/Android view bridged through RCTViewManager. The ad-rendering path no longer uses react-native-webview — it runs a Prebid Mobile auction natively and renders the winning bid in AdManagerBannerView (iOS) or AdManagerAdView (Android).
import { SellwildBanner } from '@sellwild/react-native-sdk';
<SellwildBanner
config={config}
size="320x50"
zoneId="home_top_banner"
onImpression={() => console.log('impression')}
onClick={() => console.log('click')}
onError={(err) => console.warn('failed', err.message)}
/>Props
| Prop | Type | Description |
|---|---|---|
config | SellwildConfig | Resolved config (typically from configure(partnerCode, slug)). |
size | AdSize | Banner size — one of '320x50', '300x250', '728x90', '160x600', '300x600', '1x1'. |
zoneId | number | string | Ad zone / placement identifier, mapped to a GAM ad unit on the server. |
style | ViewStyle? | Optional override for the host View style. The component already locks width/height to the chosen size. |
Events
| Event | Signature | Fires when |
|---|---|---|
onImpression | () => void | GMA reports an impression on the rendered creative. |
onClick | () => void | The user taps the ad. |
onError | (err: Error) => void | The Prebid auction or GMA load fails. err.message contains a human-readable reason. |
Required platform setup. GMA still needs GADApplicationIdentifier (iOS Info.plist) and com.google.android.gms.ads.APPLICATION_ID (Android AndroidManifest.xml) to initialize. See the iOS Configuration and Android Configuration sections below.
Marketplace listings. <SellwildWidget> is a separate component for the marketplace listings surface and is not part of the ad path. If your integration only requires banner ads, you do not need to install react-native-webview or use this component.
iOS Configuration
Info.plist
Add the following entries to ios/<YourApp>/Info.plist.
App Transport Security
Allow the SDK to communicate with Sellwild auction and widget endpoints over HTTPS. If your app already permits arbitrary loads, this block is not required.
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoadsInWebContent</key>
<true/>
<key>NSExceptionDomains</key>
<dict>
<key>prebid.sellwild.com</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<false/>
<key>NSExceptionRequiresForwardSecrecy</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
<key>widget.sellwild.com</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<false/>
<key>NSExceptionRequiresForwardSecrecy</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
</dict>
</dict>User Tracking (ATT)
Required if you intend to pass IDFA to demand partners for improved fill rates.
<key>NSUserTrackingUsageDescription</key>
<string>This app uses your advertising identifier to deliver personalized ads and measure campaign performance.</string>Android Configuration
AndroidManifest.xml
Add the following to android/app/src/main/AndroidManifest.xml.
Internet permission
<uses-permission android:name="android.permission.INTERNET" />Cleartext traffic (development only)
Required when testing against local Prebid Server instances or non-HTTPS staging environments. Remove or restrict for production builds.
<application
android:usesCleartextTraffic="true"
...>For production, use a network security configuration instead:
<!-- android/app/src/main/res/xml/network_security_config.xml -->
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="false">
<domain includeSubdomains="true">prebid.sellwild.com</domain>
<domain includeSubdomains="true">widget.sellwild.com</domain>
<domain includeSubdomains="true">cache.sellwild.com</domain>
</domain-config>
</network-security-config>Reference it in your manifest:
<application
android:networkSecurityConfig="@xml/network_security_config"
...>Basic Integration
The following App.tsx demonstrates a complete integration with banner ads and the full listing widget.
Recommended: use configure()
For most integrations, call configure(partnerCode, slug) instead of building a config object by hand. It fetches your partner config from https://widget.sellwild.com/app/{partnerCode}/{slug}.json and populates every field below automatically:
import { configure } from '@sellwild/react-native-sdk';
const config = await configure('weatherbug', 'weatherbug-weatherbug');The static example below is shown only to document each field.
// App.tsx
import React, { useCallback } from 'react';
import { SafeAreaView, ScrollView, StyleSheet, Alert } from 'react-native';
import {
SellwildWidget,
SellwildBanner,
buildConfig,
type SellwildConfig,
type SellwildListing,
type PartialSellwildConfig,
} from '@sellwild/react-native-sdk';
// ---------------------------------------------------------------------------
// 1. Configure the SDK
// ---------------------------------------------------------------------------
const partialConfig: PartialSellwildConfig = {
partnerCode: 'weatherbug',
// Mobile app identity -- required for proper in-app bid classification.
appBundleId: 'com.aws.android',
appStoreUrl: 'https://play.google.com/store/apps/details?id=com.aws.android',
// Prebid Server S2S -- routes header bidding through Sellwild's managed
// Prebid Server instance instead of running client-side adapters.
prebidServer: {
accountId: 'weatherbug',
endpoint: 'https://prebid.sellwild.com/openrtb2/auction',
bidders: ['appnexus', 'rubicon', 'ix', 'openx'],
timeout: 1500,
},
// Display customization
titleColor: '#1a1a1a',
priceColor: '#2d8c3c',
priceFontColor: '#ffffff',
fontSize: 13,
};
// Build a full config object for components that require it.
const sdkConfig: SellwildConfig = buildConfig(partialConfig);
// Tip: in 1.2.0+ the first-class integration path is
// `await configure('weatherbug', 'weatherbug-weatherbug')`, which fetches every
// runtime field — ad zones, refresh intervals, waterfall
// partners — from the Sellwild CDN. See `Configuration > Remote Config`.
// ---------------------------------------------------------------------------
// 2. Application root
// ---------------------------------------------------------------------------
export default function App() {
const handleImpression = useCallback(() => {
console.log('[Sellwild] Banner impression recorded');
}, []);
const handleBannerError = useCallback((error: Error) => {
console.warn('[Sellwild] Banner error:', error.message);
}, []);
const handleListingPress = useCallback((listing: SellwildListing) => {
// Navigate to the listing URL or a detail screen.
if (listing.url) {
console.log('[Sellwild] Listing tapped:', listing.url);
}
}, []);
return (
<SafeAreaView style={styles.container}>
<ScrollView>
{/* ----------------------------------------------------------------- */}
{/* Mobile leaderboard banner -- 320x50 */}
{/* ----------------------------------------------------------------- */}
<SellwildBanner
config={sdkConfig}
size="320x50"
zoneId={12345}
onImpression={handleImpression}
onError={handleBannerError}
style={styles.banner}
/>
{/* ----------------------------------------------------------------- */}
{/* Full listing widget with embedded Prebid header bidding */}
{/* ----------------------------------------------------------------- */}
<SellwildWidget
config={partialConfig}
onListingPress={handleListingPress}
onAdImpression={(zoneId) => {
console.log('[Sellwild] Widget ad impression, zone:', zoneId);
}}
onError={(error) => {
console.warn('[Sellwild] Widget error:', error.message);
}}
onLoad={() => {
console.log('[Sellwild] Widget loaded');
}}
style={styles.widget}
/>
{/* ----------------------------------------------------------------- */}
{/* Medium rectangle banner -- 300x250 */}
{/* ----------------------------------------------------------------- */}
<SellwildBanner
config={sdkConfig}
size="300x250"
zoneId={12346}
onImpression={handleImpression}
onError={handleBannerError}
style={styles.mediumRectangle}
/>
</ScrollView>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f8f9fa',
},
banner: {
alignSelf: 'center',
marginVertical: 12,
},
widget: {
height: 420,
marginHorizontal: 8,
},
mediumRectangle: {
alignSelf: 'center',
marginVertical: 16,
},
});Component Reference
| Component | Config Type | Description |
|---|---|---|
SellwildWidget | PartialSellwildConfig | Full listing carousel with embedded Prebid auctions |
SellwildBanner | SellwildConfig (full) | Standalone IAB banner ad unit |
SellwildWidget calls buildConfig() internally. SellwildBanner requires a pre-built SellwildConfig -- call buildConfig() before passing it.
Native Listing Cards
For full control over layout and rendering, use the useSellwildListings hook with the SellwildListingCard component. This approach renders listings as native React Native views.
Basic FlatList integration
import React, { useCallback } from 'react';
import {
FlatList,
View,
StyleSheet,
ActivityIndicator,
Text,
RefreshControl,
Linking,
} from 'react-native';
import {
useSellwildListings,
SellwildListingCard,
SellwildBanner,
buildConfig,
type SellwildConfig,
type SellwildListing,
} from '@sellwild/react-native-sdk';
const sdkConfig: SellwildConfig = buildConfig({
partnerCode: 'weatherbug',
appBundleId: 'com.aws.android',
prebidServer: {
accountId: 'weatherbug',
endpoint: 'https://prebid.sellwild.com/openrtb2/auction',
bidders: ['appnexus', 'rubicon', 'ix', 'openx'],
timeout: 1500,
},
});
// ---------------------------------------------------------------------------
// Interleave ad placements between organic listings
// ---------------------------------------------------------------------------
type FeedItem =
| { type: 'listing'; data: SellwildListing }
| { type: 'ad'; zoneId: number };
function buildFeed(listings: SellwildListing[]): FeedItem[] {
const feed: FeedItem[] = [];
listings.forEach((listing, index) => {
feed.push({ type: 'listing', data: listing });
// Insert an ad after every 4th listing
if ((index + 1) % 4 === 0) {
feed.push({ type: 'ad', zoneId: 12345 + Math.floor(index / 4) });
}
});
return feed;
}
export default function ListingFeed() {
const { listings, loading, error, refresh } = useSellwildListings(sdkConfig);
const feed = buildFeed(listings);
const handlePress = useCallback((listing: SellwildListing) => {
if (listing.url) {
Linking.openURL(listing.url);
}
}, []);
const renderItem = useCallback(
({ item }: { item: FeedItem }) => {
if (item.type === 'ad') {
return (
<SellwildBanner
config={sdkConfig}
size="300x250"
zoneId={item.zoneId}
onImpression={() => console.log('[Ad] Impression:', item.zoneId)}
onError={(err) => console.warn('[Ad] Error:', err.message)}
style={styles.adCard}
/>
);
}
return (
<SellwildListingCard
listing={item.data}
config={sdkConfig}
onPress={handlePress}
style={styles.listingCard}
/>
);
},
[handlePress],
);
const keyExtractor = useCallback(
(item: FeedItem, index: number) =>
item.type === 'listing' ? `listing-${item.data.id}` : `ad-${index}`,
[],
);
if (loading && listings.length === 0) {
return (
<View style={styles.center}>
<ActivityIndicator size="large" />
</View>
);
}
if (error) {
return (
<View style={styles.center}>
<Text style={styles.errorText}>Failed to load listings.</Text>
<Text style={styles.errorDetail}>{error.message}</Text>
</View>
);
}
return (
<FlatList
data={feed}
renderItem={renderItem}
keyExtractor={keyExtractor}
numColumns={2}
columnWrapperStyle={styles.row}
contentContainerStyle={styles.list}
refreshControl={
<RefreshControl refreshing={loading} onRefresh={refresh} />
}
/>
);
}
const styles = StyleSheet.create({
list: {
padding: 8,
},
row: {
justifyContent: 'space-between',
marginBottom: 8,
},
listingCard: {
flex: 1,
maxWidth: '48%',
marginHorizontal: 4,
},
adCard: {
alignSelf: 'center',
marginVertical: 12,
},
center: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 24,
},
errorText: {
fontSize: 16,
fontWeight: '600',
color: '#c0392b',
},
errorDetail: {
fontSize: 13,
color: '#666',
marginTop: 4,
},
});useSellwildListings Hook API
function useSellwildListings(config: SellwildConfig): UseSellwildListingsResult;
interface UseSellwildListingsResult {
listings: SellwildListing[]; // Fetched listing data
config: Record<string, unknown>; // Remote widget configuration
loading: boolean; // True during fetch or refresh
error: Error | null; // Network or parse error, if any
refresh: () => void; // Trigger a cache-busting re-fetch
}Calling refresh() clears the internal listing cache and re-fetches from the network. Use it with RefreshControl for pull-to-refresh.
Direct Prebid Server Auction
For advanced integrations where you need full control over the bidding process, you can call the Prebid Server auction endpoint directly using fetch(). This bypasses <SellwildBanner> entirely and gives you raw bid responses to render however you choose.
OpenRTB Bid Request
interface PrebidAuctionRequest {
id: string;
imp: Array<{
id: string;
banner: {
format: Array<{ w: number; h: number }>;
};
ext: {
prebid: {
bidder: Record<string, Record<string, unknown>>;
};
};
}>;
app: {
bundle: string;
publisher: { id: string };
storeurl?: string;
};
device: {
ua: string;
ip?: string;
os: string;
osv?: string;
};
regs?: {
ext?: { gdpr?: number };
};
user?: {
ext?: { consent?: string };
};
tmax: number;
}Sending an Auction Request
async function runPrebidAuction(): Promise<void> {
const request: PrebidAuctionRequest = {
id: `auction-${Date.now()}`,
imp: [
{
id: 'banner-1',
banner: {
format: [{ w: 320, h: 50 }],
},
ext: {
prebid: {
bidder: {
appnexus: { placement_id: 13144370 },
ix: { siteId: '345678' },
},
},
},
},
{
id: 'banner-2',
banner: {
format: [{ w: 300, h: 250 }],
},
ext: {
prebid: {
bidder: {
appnexus: { placement_id: 13144371 },
openx: { unit: '540012345', delDomain: 'weatherbug-d.openx.net' },
},
},
},
},
],
app: {
bundle: 'com.aws.android',
publisher: { id: 'weatherbug' },
storeurl: 'https://play.google.com/store/apps/details?id=com.aws.android',
},
device: {
ua: 'Mozilla/5.0 (Linux; Android 14; Pixel 8)',
os: 'Android',
osv: '14',
},
tmax: 1500,
};
try {
const response = await fetch(
'https://prebid.sellwild.com/openrtb2/auction',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request),
},
);
if (!response.ok) {
throw new Error(`Auction failed: ${response.status} ${response.statusText}`);
}
const auctionResult = await response.json();
handleAuctionResponse(auctionResult);
} catch (error) {
console.error('[Prebid] Auction error:', error);
}
}Parsing Bid Responses
The Prebid Server response follows the OpenRTB 2.6 BidResponse schema:
interface PrebidBidResponse {
id: string;
seatbid: Array<{
bid: Array<{
id: string;
impid: string; // Matches imp[].id from the request
price: number; // CPM in USD
adm: string; // Ad markup (HTML creative)
adid: string;
adomain: string[]; // Advertiser domains
w: number;
h: number;
ext?: {
prebid?: {
type: string; // "banner", "video", "native"
targeting?: Record<string, string>;
};
};
}>;
seat: string; // Bidder code (e.g., "appnexus")
}>;
cur: string; // Currency code
ext?: {
responsetimemillis?: Record<string, number>; // Per-bidder latency
errors?: Record<string, Array<{ code: number; message: string }>>;
};
}
function handleAuctionResponse(response: PrebidBidResponse): void {
if (!response.seatbid?.length) {
console.log('[Prebid] No bids returned (no fill)');
return;
}
// Collect all bids, grouped by impression ID
const bidsByImp = new Map<string, Array<{ seat: string; price: number; adm: string }>>();
for (const seatbid of response.seatbid) {
for (const bid of seatbid.bid) {
const existing = bidsByImp.get(bid.impid) ?? [];
existing.push({
seat: seatbid.seat,
price: bid.price,
adm: bid.adm,
});
bidsByImp.set(bid.impid, existing);
}
}
// Select the winning bid for each impression (highest CPM)
for (const [impId, bids] of bidsByImp) {
const winner = bids.reduce((a, b) => (a.price > b.price ? a : b));
console.log(
`[Prebid] Winner for ${impId}: ${winner.seat} at $${winner.price.toFixed(2)} CPM`,
);
// Render winner.adm yourself (see "Rendering Winning Creatives" below).
}
// Log per-bidder latency for diagnostics
if (response.ext?.responsetimemillis) {
for (const [bidder, ms] of Object.entries(response.ext.responsetimemillis)) {
console.log(`[Prebid] ${bidder} responded in ${ms}ms`);
}
}
}Rendering Winning Creatives
Note: This is an advanced escape hatch for partners who want to render
admthemselves instead of using<SellwildBanner>. It does not use Google Ad Manager or the SDK's native ad path -- you are responsible for impression tracking, viewability measurement, and click handling. The supported integration is<SellwildBanner>, which renders throughAdManagerBannerView(iOS) orAdManagerAdView(Android) without a WebView.
If you choose to render the winning adm yourself, the simplest approach is to wrap the HTML creative in a WebView. You will need to install react-native-webview for this path.
import React from 'react';
import { View } from 'react-native';
import { WebView } from 'react-native-webview';
interface WinningBidProps {
adMarkup: string;
width: number;
height: number;
}
function WinningBidRenderer({ adMarkup, width, height }: WinningBidProps) {
const html = `<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
* { margin: 0; padding: 0; }
html, body { width: ${width}px; height: ${height}px; overflow: hidden; }
</style>
</head>
<body>${adMarkup}</body>
</html>`;
return (
<View style={{ width, height }}>
<WebView
source={{ html }}
style={{ width, height }}
javaScriptEnabled
domStorageEnabled
scrollEnabled={false}
showsVerticalScrollIndicator={false}
showsHorizontalScrollIndicator={false}
originWhitelist={['*']}
/>
</View>
);
}Prebid Server Configuration
The prebidServer object on SellwildConfig controls server-side header bidding. When present, <SellwildBanner> bootstraps Prebid Mobile with these values and routes every auction through the specified Prebid Server instance.
Configuration Object
interface PrebidServerConfig {
/** Your Prebid Server account ID. */
accountId: string;
/**
* Full URL to the Prebid Server auction endpoint.
* Sellwild's managed instance: https://prebid.sellwild.com/openrtb2/auction
*/
endpoint: string;
/**
* Bidder codes to route through Prebid Server.
* Must match the adapter names configured on the server.
*/
bidders: string[];
/** Server-side auction timeout in milliseconds. Default: 1500. */
timeout?: number;
/**
* Cookie sync endpoint. Derived from `endpoint` if omitted.
* Only needed if your Prebid Server uses a non-standard path.
*/
syncEndpoint?: string;
}Field Reference
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
accountId | string | Yes | -- | Your Prebid Server account ID |
endpoint | string | Yes | -- | Full auction endpoint URL |
bidders | string[] | Yes | -- | Bidder adapter codes for server-side routing |
timeout | number | No | 1500 | Auction timeout in milliseconds |
syncEndpoint | string | No | Derived | /cookie_sync URL override |
Example
prebidServer: {
accountId: 'weatherbug',
endpoint: 'https://prebid.sellwild.com/openrtb2/auction',
bidders: ['appnexus', 'rubicon', 'ix', 'openx'],
timeout: 1500,
}When prebidServer is set, <SellwildBanner>:
- Bootstraps Prebid Mobile with
accountId,endpoint, andtimeouton first use. - Builds an OpenRTB 2.6 banner ad unit on every
load(), including any passthrough bidder parameters fromSellwildConfig.remote. - Posts the request to your Prebid Server endpoint and applies the winning bid's targeting keywords to the underlying GAM ad request.
Metro Configuration
When developing against a local copy of the Sellwild SDK (e.g., the monorepo at ../sellwild-sdk), configure Metro to resolve the local packages.
metro.config.js
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
const path = require('path');
// Path to your local sellwild-sdk checkout
const sellwildSdkRoot = path.resolve(__dirname, '../sellwild-sdk');
const config = {
watchFolders: [
sellwildSdkRoot,
],
resolver: {
// Ensure Metro does not resolve duplicate React/React Native copies
// from the SDK's own node_modules.
nodeModulesPaths: [
path.resolve(__dirname, 'node_modules'),
],
// Redirect @sellwild/* imports to the local source
extraNodeModules: {
'@sellwild/sdk-core': path.resolve(sellwildSdkRoot, 'core'),
'@sellwild/react-native-sdk': path.resolve(sellwildSdkRoot, 'react-native'),
},
// Block duplicate copies of these packages from the SDK workspace
blockList: [
new RegExp(
path.resolve(sellwildSdkRoot, 'node_modules', 'react-native') + '/.*',
),
new RegExp(
path.resolve(sellwildSdkRoot, 'node_modules', 'react') + '/.*',
),
],
},
};
module.exports = mergeConfig(getDefaultConfig(__dirname), config);Common Metro issues
| Symptom | Cause | Fix |
|---|---|---|
Unable to resolve module @sellwild/sdk-core | Missing extraNodeModules entry | Add the path mapping shown above |
Duplicate module name: react | SDK workspace has its own react copy | Add blockList entries |
EMFILE: too many open files | Watchman watching too many directories | Add .watchmanconfig with "ignore_dirs" |
GDPR and Privacy
The Sellwild SDK supports GDPR consent signaling through the Prebid Server auction flow. When prebidServer is configured, Prebid Mobile attaches regs.ext.gdpr and user.ext.consent to every OpenRTB request, and the Google Mobile Ads SDK consumes the same TCF values for its own EU User Consent enforcement.
Configuration
Pass GDPR parameters through the SellwildConfig:
const config: PartialSellwildConfig = {
partnerCode: 'weatherbug',
// GDPR consent signals
gdprApplies: true,
tcString: 'CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA',
// IAB TCF version (2 = TCF v2.x)
tcfVersion: 2,
// GPP (Global Privacy Platform) support
gppEnabled: true,
prebidServer: {
accountId: 'weatherbug',
endpoint: 'https://prebid.sellwild.com/openrtb2/auction',
bidders: ['appnexus', 'rubicon', 'ix', 'openx'],
timeout: 1500,
},
};How Consent Flows Through the SDK
- The host app collects consent through its own CMP (OneTrust, Didomi, etc.). Per the IAB TCF v2.x specification, the CMP writes
IABTCF_TCStringandIABTCF_gdprAppliestoUserDefaults(iOS) andSharedPreferences(Android). - Prebid Mobile reads those keys directly when building each OpenRTB request and attaches them as
regs.ext.gdpranduser.ext.consent. - Prebid Server forwards the consent string to all bidders. Bidders that lack consent for required purposes are excluded from the auction.
You can also pass gdprApplies and tcString explicitly through SellwildConfig if your app does not write the standard IAB keys.
Direct Auction Requests
When calling the Prebid Server auction endpoint directly via fetch(), include GDPR signals in the OpenRTB request body:
const request = {
// ...imp, app, device...
regs: {
ext: { gdpr: 1 },
},
user: {
ext: {
consent: 'CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA',
},
},
tmax: 1500,
};Important Notes
- Prebid Server defaults to
gdpr=1(GDPR applies). If no consent string is present, all bidder calls are suppressed. Always explicitly setgdprApplies: falsefor non-GDPR traffic. - Native CMP bridging is not automatic. If the host app uses a native CMP, it must read the consent string from
NSUserDefaults(key:IABTCF_TCString) orSharedPreferencesand pass it throughSellwildConfig. - CCPA/US Privacy is supported through the GPP framework when
gppEnabled: true.
TypeScript Reference
The SDK exports all types from @sellwild/react-native-sdk for convenience. The canonical definitions live in @sellwild/sdk-core.
Exported Types
// Configuration
import type {
SellwildConfig,
PartialSellwildConfig,
PrebidServerConfig,
} from '@sellwild/react-native-sdk';
// Listings
import type {
SellwildListing,
SellwildPhoto,
SellwildUser,
SellwildListingsResponse,
} from '@sellwild/react-native-sdk';
// Ads
import type {
AdSize, // '300x250' | '320x50' | '728x90' | '160x600' | '300x600' | '1x1'
AdPlacement,
AdPlacementType, // 'banner_top' | 'banner_bottom' | 'inline' | 'interstitial'
} from '@sellwild/react-native-sdk';
// Component props
import type {
SellwildWidgetProps,
SellwildBannerProps,
SellwildListingCardProps,
UseSellwildListingsResult,
} from '@sellwild/react-native-sdk';Key Interfaces
SellwildConfig (abridged)
interface SellwildConfig {
// Required
partnerCode: string;
// App identity
appBundleId?: string;
appStoreUrl?: string;
// Prebid Server
prebidServer?: PrebidServerConfig;
// Display
titleColor: string;
priceColor: string;
priceFontColor: string;
fontSize: number;
fontFamily: string;
fontColor: string;
cardWidth: string;
cardHeight: string;
colors: string[];
// Ads
bannerZid: number | string;
gamTag: string;
adRefreshMax: number;
adRefreshMaxMobile: number;
adRefreshInterval: number;
// Privacy
gppEnabled: boolean;
tcfVersion: number;
// Debug
debug: boolean;
}SellwildListing
interface SellwildListing {
id: string;
status: string;
title: string;
text: string;
url: string;
categoryId: string;
categoryGroupId: string;
currency: string;
price: string;
strikePrice: string;
has_photo: boolean;
photo_count: number;
photos: SellwildPhoto[];
createdDate: string;
videoUrl: string;
shippable: string;
listingType: string;
dataSourceId: string;
user: SellwildUser;
distance?: number;
}SellwildPhoto
interface SellwildPhoto {
url: string;
thumbUrl: string;
background?: string;
}SellwildUser
interface SellwildUser {
id: string;
firstName: string;
lastName: string;
username: string;
membershipType: string;
trustLevel: string;
has_photo: string;
photos: SellwildPhoto[];
isPhoneVerified: boolean;
}Utility Functions
import {
buildConfig,
currencyToSymbol,
type SellwildConfig,
} from '@sellwild/react-native-sdk';
// Build a full SellwildConfig from partial input with defaults applied
const config: SellwildConfig = buildConfig({
partnerCode: 'weatherbug',
});
// Convert ISO currency code to symbol
currencyToSymbol('USD'); // '$'
currencyToSymbol('EUR'); // '\u20ac'
currencyToSymbol('GBP'); // '\u00a3'Troubleshooting
Metro bundler cannot resolve @sellwild/sdk-core
Symptom: error: Error: Unable to resolve module @sellwild/sdk-core
Cause: The core package is not installed or Metro cannot locate it.
Fix:
# Verify the package is installed
npm ls @sellwild/sdk-core
# If missing, reinstall
npm install @sellwild/react-native-sdk
# Clear Metro cache and restart
npx react-native start --reset-cacheIf developing against a local SDK checkout, ensure extraNodeModules is configured in metro.config.js (see Metro Configuration).
pod install fails on iOS
Symptom: [!] CocoaPods could not find compatible versions for pod "SellwildSDK" or "react-native-webview".
Fix:
cd ios
pod deintegrate
pod cache clean --all
pod install --repo-update
cd ..If using Xcode 15+, ensure your deployment target is iOS 13.0 or later in ios/Podfile:
platform :ios, '13.0'App Transport Security (ATS) blocks network requests
Symptom: iOS console shows App Transport Security has blocked a cleartext HTTP resource load.
Cause: The SDK endpoints use HTTPS, but a misconfigured proxy or redirect may downgrade to HTTP.
Fix: Add the ATS exception domains listed in iOS Configuration. For development with a local Prebid Server over HTTP, temporarily allow arbitrary loads:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>Remove this before submitting to the App Store.
No fill -- banner renders blank
Symptom: <SellwildBanner> mounts but no ad creative appears, or onAdFailed fires with a "no fill" reason.
Cause: No bidder returned a bid above floor price, the configured GAM ad unit is empty, or the bidder configuration is incorrect.
Diagnostic steps:
- Enable debug mode:
const config = buildConfig({
partnerCode: 'weatherbug',
debug: true,
});Inspect Prebid Mobile and GMA logs natively. On iOS, run the app under Xcode and filter the console by
Sellwild,PrebidMobile, andGoogle. On Android, runadb logcatand filter by the same tags.Verify the Prebid Server is responding:
curl -X POST https://prebid.sellwild.com/openrtb2/auction \
-H "Content-Type: application/json" \
-d '{
"id": "test-1",
"imp": [{
"id": "imp-1",
"banner": { "format": [{ "w": 320, "h": 50 }] },
"ext": {
"prebid": {
"bidder": {
"appnexus": { "placement_id": 13144370 }
}
}
}
}],
"app": {
"bundle": "com.aws.android",
"publisher": { "id": "weatherbug" }
},
"tmax": 1500
}'- Check the response for
ext.errorsorext.responsetimemillisto identify bidder-level issues.
Banner re-mounts on every parent render
Symptom: The banner resets or re-runs the auction when unrelated state changes in the parent component.
Cause: A new config object reference is created on each render, causing the native bridge to tear down and rebuild the underlying ad view.
Fix: Memoize the config object so it has a stable identity across renders:
const sdkConfig = useMemo(
() => buildConfig({ partnerCode: 'weatherbug' }),
[],
);Android cleartext traffic error
Symptom: java.io.IOException: Cleartext HTTP traffic to prebid.sellwild.com not permitted
Cause: Android 9+ blocks cleartext HTTP by default. The Sellwild production endpoints use HTTPS, so this error indicates either a redirect issue or a local development server.
Fix: See Android Configuration for network security config setup.
useSellwildListings refresh returns stale data
Symptom: Calling refresh() does not fetch new listings.
Cause: This was a known issue in earlier SDK versions where the listing cache was not cleared on refresh. Current versions call clearListingCache() when refreshKey > 0.
Fix: Update to the latest version of @sellwild/react-native-sdk:
npm install @sellwild/react-native-sdk@latest