Partner Native Bridge Protocol
The Trillboards Lite SDK includes a Universal Native Bridge that enables seamless communication between the SDK (running in a WebView/iframe) and your native host application. This allows you to receive ad events and send commands from Android, iOS, React Native, Flutter, CTV, Electron, and other platforms.
Overview
When embedding the Trillboards Lite SDK in a native application, you need a way to:
- Receive events when ads start, complete, error, or have no fill
- Send commands to show/hide ads, skip, or refresh
The Native Bridge handles this by:
- Auto-detecting standard bridge interfaces (Android, iOS, React Native, etc.)
- Allowing custom bridge registration for non-standard integrations
- Using a standardized event protocol across all platforms
Quick Start (Zero Config)
For most platforms, the SDK auto-detects the native bridge. Just expose the standard interface in your native code:
Android WebView
// In your WebView setup
webView.addJavascriptInterface(object {
@JavascriptInterface
fun onEvent(payload: String) {
val event = JSONObject(payload)
when (event.getString("event")) {
"ad_started" -> handleAdStarted(event.getJSONObject("data"))
"ad_completed" -> handleAdCompleted(event.getJSONObject("data"))
"ad_error" -> handleAdError(event.getJSONObject("data"))
"no_ads" -> handleNoAds(event.getJSONObject("data"))
}
}
}, "TrillboardsBridge")
iOS WKWebView
// In your WKWebView setup
let contentController = webView.configuration.userContentController
contentController.add(self, name: "trillboards")
// Implement WKScriptMessageHandler
extension YourViewController: WKScriptMessageHandler {
func userContentController(_ controller: WKUserContentController,
didReceive message: WKScriptMessage) {
guard message.name == "trillboards",
let body = message.body as? [String: Any],
let event = body["event"] as? String else { return }
switch event {
case "ad_started": handleAdStarted(body["data"] as? [String: Any])
case "ad_completed": handleAdCompleted(body["data"] as? [String: Any])
case "ad_error": handleAdError(body["data"] as? [String: Any])
case "no_ads": handleNoAds(body["data"] as? [String: Any])
default: break
}
}
}
React Native WebView
import { WebView } from 'react-native-webview';
function AdPlayer() {
const handleMessage = (event: WebViewMessageEvent) => {
const data = JSON.parse(event.nativeEvent.data);
if (data.type !== 'trillboards') return;
switch (data.event) {
case 'ad_started':
console.log('Ad started:', data.data);
break;
case 'ad_completed':
console.log('Ad completed:', data.data);
break;
case 'ad_error':
console.log('Ad error:', data.data);
break;
case 'no_ads':
console.log('No ads available:', data.data);
break;
}
};
return (
<WebView
source={{ uri: 'https://screen.trillboards.com/partner-sdk/embed.html?device=YOUR_DEVICE_ID' }}
onMessage={handleMessage}
javaScriptEnabled
mediaPlaybackRequiresUserAction={false}
/>
);
}
Flutter WebView
import 'package:webview_flutter/webview_flutter.dart';
import 'dart:convert';
class AdPlayerWidget extends StatefulWidget {
@override
_AdPlayerWidgetState createState() => _AdPlayerWidgetState();
}
class _AdPlayerWidgetState extends State<AdPlayerWidget> {
late WebViewController _controller;
@override
Widget build(BuildContext context) {
return WebViewWidget(
controller: WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..addJavaScriptChannel(
'Flutter',
onMessageReceived: (message) {
final data = jsonDecode(message.message);
if (data['type'] != 'trillboards') return;
switch (data['event']) {
case 'ad_started':
print('Ad started: ${data['data']}');
break;
case 'ad_completed':
print('Ad completed: ${data['data']}');
break;
case 'ad_error':
print('Ad error: ${data['data']}');
break;
case 'no_ads':
print('No ads: ${data['data']}');
break;
}
},
)
..loadRequest(Uri.parse(
'https://screen.trillboards.com/partner-sdk/embed.html?device=YOUR_DEVICE_ID'
)),
);
}
}
Auto-Detection Matrix
The SDK automatically detects these interfaces (in order):
| Platform | Detection | Interface |
|---|---|---|
| Android WebView | window.TrillboardsBridge.onEvent | TrillboardsBridge.onEvent(jsonString) |
| Android (alt) | window.Android.onTrillboardsEvent | Android.onTrillboardsEvent(jsonString) |
| iOS WKWebView | window.webkit.messageHandlers.trillboards | trillboards.postMessage(object) |
| React Native | window.ReactNativeWebView.postMessage | ReactNativeWebView.postMessage(jsonString) |
| Flutter | window.Flutter.postMessage | Flutter.postMessage(jsonString) |
| CTV (Tizen/webOS/Fire TV) | window.__TRILL_CTV_BRIDGE__.postEvent | __TRILL_CTV_BRIDGE__.postEvent(jsonString) |
| Electron | window.electronAPI.trillboardsEvent | electronAPI.trillboardsEvent(object) |
| Tauri | window.__TAURI__.event | Emits trillboards-event |
| Iframe embed | window.parent !== window | parent.postMessage(object, '*') |
Custom Bridge Registration
For non-standard integrations, register a custom bridge:
// In your WebView's JavaScript context
TrillboardsLite.registerBridge({
// Required: how to send events to host
send: function(event, data) {
// Your custom transport
MyCustomBridge.sendEvent(JSON.stringify({ event, data }));
},
// Optional: receive commands from host
receive: function(callback) {
// Set up listener for host commands
MyCustomBridge.onCommand = callback;
},
// Optional: filter which events to send (reduces noise)
events: ['ad_started', 'ad_completed', 'ad_error', 'no_ads'],
// Optional: bridge name for debugging
name: 'MyCustomBridge'
});
Event Protocol
All events follow this standardized payload structure:
interface TrillboardsEvent {
type: 'trillboards'; // Message type identifier
version: '1.0'; // Protocol version
event: string; // Event name (see table below)
data: object; // Event-specific data
timestamp: number; // Unix timestamp (ms)
deviceId: string; // Device identifier
screenId?: string; // Screen identifier (if linked)
sessionId: string; // Session identifier
}
Events Reference
| Event | Description | Data |
|---|---|---|
ready | SDK initialized | { deviceId, screenId, adCount, waterfallMode } |
ad_started | Ad began playing | { type: 'programmatic'|'image'|'video', adId?, url? } |
ad_completed | Ad finished naturally | { type, reason: 'complete', completed: true } |
ad_skipped | User skipped ad | { type } |
ad_ended | Ad playback ended | { type?, reason? } |
ad_error | Ad failed | { type, error: { message, code? } } |
no_ads | No fill available | { reason: 'no_fill'|'offline'|'programmatic_unavailable' } |
overlay_clicked | User tapped CTA | {} |
ads_refreshed | Ad list updated | { count } |
online | Network restored | {} |
offline | Network lost | {} |
state_changed | State response | Full state object (see getState()) |
Sending Commands to SDK
Your native app can send commands to control the SDK.
From Android
// Show an ad
webView.evaluateJavascript("TrillboardsLite.show()", null)
// Hide the player
webView.evaluateJavascript("TrillboardsLite.hide()", null)
// Skip current ad
webView.evaluateJavascript("TrillboardsLite.skipAd()", null)
// Refresh ad list
webView.evaluateJavascript("TrillboardsLite.refresh()", null)
From iOS
// Show an ad
webView.evaluateJavaScript("TrillboardsLite.show()")
// Hide the player
webView.evaluateJavaScript("TrillboardsLite.hide()")
From React Native
const webViewRef = useRef<WebView>(null);
// Show an ad
webViewRef.current?.injectJavaScript('TrillboardsLite.show(); true;');
// Hide the player
webViewRef.current?.injectJavaScript('TrillboardsLite.hide(); true;');
Via postMessage (Iframe)
// From parent window to iframe
const iframe = document.getElementById('trillboards-iframe');
// Show an ad
iframe.contentWindow.postMessage({
type: 'trillboards-command',
action: 'show'
}, '*');
// Hide the player
iframe.contentWindow.postMessage({
type: 'trillboards-command',
action: 'hide'
}, '*');
// Get current state (response via state_changed event)
iframe.contentWindow.postMessage({
type: 'trillboards-command',
action: 'getState'
}, '*');
// Update configuration
iframe.contentWindow.postMessage({
type: 'trillboards-command',
action: 'configure',
params: { waterfall: 'programmatic_only', volume: 0 }
}, '*');
Command Reference
| Command | Description | Parameters |
|---|---|---|
show | Request and play an ad | { immediate?: boolean } |
hide | Stop and hide player | { silent?: boolean } |
skip | Skip current ad | {} |
refresh | Refresh ad list from API | {} |
getState | Request current state | {} (responds with state_changed) |
configure | Update settings | { waterfall?, volume? } |
Platform-Specific Guides
CTV (Smart TVs)
For Tizen (Samsung), webOS (LG), Fire TV, and Android TV:
// On your CTV app, expose this interface before loading the WebView
window.__TRILL_CTV_BRIDGE__ = {
postEvent: function(jsonString) {
// Parse and route to your CTV app's event system
const event = JSON.parse(jsonString);
TizenBridge.sendEvent(event.event, event.data);
}
};
Electron
// In preload.js
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', {
trillboardsEvent: (data) => {
ipcRenderer.send('trillboards-event', data);
}
});
// In main.js
ipcMain.on('trillboards-event', (event, data) => {
console.log('Trillboards event:', data.event, data.data);
});
Tauri
// In your Tauri app
use tauri::Manager;
fn main() {
tauri::Builder::default()
.setup(|app| {
let window = app.get_window("main").unwrap();
window.listen("trillboards-event", |event| {
println!("Trillboards event: {:?}", event.payload());
});
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
Capacitor/Cordova
// Using @capawesome/capacitor-app-webview
import { AppWebview } from '@capawesome/capacitor-app-webview';
// Listen for messages
AppWebview.addListener('message', (event) => {
const data = JSON.parse(event.message);
if (data.type === 'trillboards') {
console.log('Trillboards event:', data.event);
}
});
Event Filtering
To reduce noise, filter events at registration:
TrillboardsLite.registerBridge({
send: (event, data) => { /* ... */ },
// Only receive these critical events
events: ['ad_started', 'ad_completed', 'ad_error', 'no_ads']
});
Best Practices
1. Handle Offline Gracefully
The SDK caches ads for offline playback. Listen for online/offline events:
"offline" -> {
// Show offline indicator or pause ad requests
}
"online" -> {
// Resume normal operation
}
2. Track Session State
Use the sessionId in events to correlate ads within a user session:
val sessionMetrics = mutableMapOf<String, MutableList<String>>()
fun handleEvent(event: JSONObject) {
val sessionId = event.getString("sessionId")
val eventName = event.getString("event")
sessionMetrics.getOrPut(sessionId) { mutableListOf() }.add(eventName)
}
3. Implement Retry Logic
When no_ads occurs, the SDK automatically retries in programmatic_only mode. For manual control:
"no_ads" -> {
// SDK handles retry automatically, but you can track
Log.d("Trillboards", "No fill - SDK will retry")
}
4. Clean Up on Destroy
override fun onDestroy() {
webView.evaluateJavascript("TrillboardsLite.destroy()", null)
super.onDestroy()
}
Troubleshooting
Events Not Received
- Check interface name: Ensure your bridge uses the exact name the SDK expects
- Check timing: Register your interface before the WebView loads the SDK
- Check console: Look for
[Trillboards] Bridge auto-detected:log - Use custom bridge: If auto-detection fails, register explicitly
Commands Not Working
- Check WebView is loaded: Wait for page load before sending commands
- Check JavaScript enabled: Ensure
javaScriptEnabled = true - Check console errors: Look for JavaScript errors in WebView
Debugging
Enable verbose logging:
// In WebView console
console.log('[Trillboards] Bridge detected:', window.TrillboardsBridge ? 'yes' : 'no');
console.log('[Trillboards] State:', TrillboardsLite.getState());
FAQ
Is /programmatic-event API for native communication?
No. The /device/{deviceId}/programmatic-event endpoint is a server-side analytics endpoint. It records events for Trillboards' internal analytics and triggers webhooks. It does NOT push events to your native app.
For native app event delivery, use the JavaScript Native Bridge described in this document.
Can I use both webhooks and native bridge?
Yes. They serve different purposes:
- Native Bridge: Real-time events to your native app UI
- Webhooks: Server-side event processing, analytics, billing integration
What if my platform isn't listed?
Use the custom bridge registration:
TrillboardsLite.registerBridge({
send: (event, data) => {
// Your custom transport mechanism
}
});
How do I test the bridge?
- Load the SDK in your WebView
- Open developer console
- Trigger an ad:
TrillboardsLite.show() - Check your native handler receives events
Related Documentation
- Lite SDK Overview
- Local Testing
- Partner CMS API
- Programmatic Settings