shared.developerDocs
examples.heroTitle examples.heroTitleAccent
examples.heroSubtitle
examples.androidTitle
examples.androidDesc
MainActivity.kt
MainActivity.kt
package com.example.vendingmachine
import android.os.Bundle
import android.webkit.*
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
private lateinit var webView: WebView
private var isShowingAd = false
companion object {
// Replace with your actual embed URL from device registration
private const val AD_URL = "https://screen.trillboards.com?fp=DEVICE_FINGERPRINT"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setupWebView()
loadAds()
}
private fun setupWebView() {
webView = findViewById(R.id.adWebView)
webView.settings.apply {
javaScriptEnabled = true
domStorageEnabled = true
mediaPlaybackRequiresUserGesture = false
mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
cacheMode = WebSettings.LOAD_DEFAULT
}
// Add JavaScript bridge
webView.addJavascriptInterface(AdBridge(), "TrillboardsNative")
// Configure WebViewClient
webView.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
// Inject iOS-compatible event bridge
injectEventBridge()
}
}
}
private fun loadAds() {
webView.loadUrl(AD_URL)
}
private fun injectEventBridge() {
// Listen for SDK events and forward to native
val script = """
window.addEventListener('trillboards:overlay_clicked', function(e) {
if (window.TrillboardsNative) {
window.TrillboardsNative.onOverlayClicked();
}
});
window.addEventListener('trillboards:ad_started', function(e) {
if (window.TrillboardsNative) {
window.TrillboardsNative.onAdStarted(
e.detail.type || 'unknown'
);
}
});
window.addEventListener('trillboards:ad_ended', function(e) {
if (window.TrillboardsNative) {
window.TrillboardsNative.onAdEnded();
}
});
""".trimIndent()
webView.evaluateJavascript(script, null)
}
// JavaScript bridge for receiving SDK events
inner class AdBridge {
@JavascriptInterface
fun onOverlayClicked() {
runOnUiThread {
// User tapped overlay - show your vending UI
showVendingInterface()
}
}
@JavascriptInterface
fun onAdStarted(adType: String) {
runOnUiThread {
isShowingAd = true
// Hide vending UI if visible
}
}
@JavascriptInterface
fun onAdEnded() {
runOnUiThread {
isShowingAd = false
// Ad finished, next ad will play after interval
}
}
}
private fun showVendingInterface() {
// Send command to SDK to hide
webView.evaluateJavascript(
"if(window.TrillboardsLite) TrillboardsLite.hide();",
null
)
// Now show your vending machine UI
// startActivity(Intent(this, VendingActivity::class.java))
}
override fun onBackPressed() {
// Prevent accidental exit during ads
if (isShowingAd) {
return
}
super.onBackPressed()
}
}activity_main.xml
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000000">
<WebView
android:id="@+id/adWebView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>AndroidManifest.xml (permissions)
AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- Optional: Keep screen on -->
<uses-permission android:name="android.permission.WAKE_LOCK" />examples.iosTitle
examples.iosDesc
AdViewController.swift
AdViewController.swift
import UIKit
import WebKit
class AdViewController: UIViewController {
private var webView: WKWebView!
private var isShowingAd = false
// Replace with your actual embed URL
private let adURL = "https://screen.trillboards.com?fp=DEVICE_FINGERPRINT"
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .black
setupWebView()
loadAds()
// Keep screen awake
UIApplication.shared.isIdleTimerDisabled = true
}
private func setupWebView() {
let config = WKWebViewConfiguration()
config.allowsInlineMediaPlayback = true
config.mediaTypesRequiringUserActionForPlayback = []
// Add message handler for SDK events
let contentController = config.userContentController
contentController.add(self, name: "trillboards")
// Inject event bridge script
let script = WKUserScript(
source: eventBridgeScript,
injectionTime: .atDocumentEnd,
forMainFrameOnly: true
)
contentController.addUserScript(script)
webView = WKWebView(frame: view.bounds, configuration: config)
webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
webView.scrollView.isScrollEnabled = false
view.addSubview(webView)
}
private var eventBridgeScript: String {
return """
window.addEventListener('trillboards:overlay_clicked', function(e) {
window.webkit.messageHandlers.trillboards.postMessage({
event: 'overlay_clicked'
});
});
window.addEventListener('trillboards:ad_started', function(e) {
window.webkit.messageHandlers.trillboards.postMessage({
event: 'ad_started',
type: e.detail?.type || 'unknown'
});
});
window.addEventListener('trillboards:ad_ended', function(e) {
window.webkit.messageHandlers.trillboards.postMessage({
event: 'ad_ended'
});
});
"""
}
private func loadAds() {
guard let url = URL(string: adURL) else { return }
webView.load(URLRequest(url: url))
}
private func showVendingInterface() {
// Hide ads
webView.evaluateJavaScript(
"if(window.TrillboardsLite) TrillboardsLite.hide();"
)
// Present your vending UI
// let vendingVC = VendingViewController()
// present(vendingVC, animated: true)
}
}
extension AdViewController: WKScriptMessageHandler {
func userContentController(
_ userContentController: WKUserContentController,
didReceive message: WKScriptMessage
) {
guard let body = message.body as? [String: Any],
let event = body["event"] as? String else { return }
switch event {
case "overlay_clicked":
showVendingInterface()
case "ad_started":
isShowingAd = true
case "ad_ended":
isShowingAd = false
default:
break
}
}
}
// Don't forget to remove message handler to prevent memory leaks
extension AdViewController {
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
webView.configuration.userContentController.removeScriptMessageHandler(
forName: "trillboards"
)
}
}examples.apiTitle
examples.nodeSubheading
examples.nodeDesc
api-usage.js
const axios = require('axios');
const API_BASE = 'https://api.trillboards.com/v1/partner';
// 1. Register as a partner (do once)
async function registerPartner() {
const response = await axios.post(`${API_BASE}/register`, {
name: 'My Vending Company',
slug: 'myvendingco',
contact_email: 'developer@myvendingco.com',
partner_type: 'vending_machine'
});
console.log('API Key:', response.data.data.api_key);
// SAVE THIS API KEY SECURELY!
return response.data.data.api_key;
}
// 2. Register devices (call for each device)
async function registerDevice(apiKey, deviceInfo) {
const response = await axios.post(
`${API_BASE}/device`,
{
device_id: deviceInfo.id,
device_type: 'vending_machine',
name: deviceInfo.name,
display: {
width: 1920,
height: 1080,
orientation: 'landscape'
},
location: {
lat: deviceInfo.lat,
lng: deviceInfo.lng,
venue_type: deviceInfo.venueType,
venue_name: deviceInfo.venueName
}
},
{
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
}
}
);
console.log('Embed URL:', response.data.data.embed_url);
return response.data.data;
}
// 3. Get analytics
async function getAnalytics(apiKey) {
const response = await axios.get(
`${API_BASE}/analytics`,
{
headers: { 'Authorization': `Bearer ${apiKey}` },
params: {
start_date: '2024-01-01',
end_date: '2024-12-31'
}
}
);
console.log('Total impressions:', response.data.data.partner.total_impressions);
console.log('Revenue (cents):', response.data.data.partner.total_revenue_cents);
return response.data.data;
}
// Usage
(async () => {
// const apiKey = await registerPartner();
const apiKey = 'YOUR_API_KEY';
await registerDevice(apiKey, {
id: 'machine-001',
name: 'Mall Location A',
lat: 44.8537,
lng: -93.2428,
venueType: 'mall',
venueName: 'Mall of America'
});
const analytics = await getAnalytics(apiKey);
})();examples.pythonSubheading
examples.pythonDesc
bulk_register.py
import requests
API_BASE = 'https://api.trillboards.com/v1/partner'
API_KEY = 'YOUR_API_KEY'
headers = {
'Authorization': f'Bearer {API_KEY}',
'Content-Type': 'application/json'
}
# Register multiple devices from a CSV or database
devices = [
{'id': 'machine-001', 'name': 'Location A', 'lat': 44.8537, 'lng': -93.2428},
{'id': 'machine-002', 'name': 'Location B', 'lat': 44.9778, 'lng': -93.2650},
{'id': 'machine-003', 'name': 'Location C', 'lat': 44.9537, 'lng': -93.0900},
]
for device in devices:
response = requests.post(
f'{API_BASE}/device',
headers=headers,
json={
'device_id': device['id'],
'device_type': 'vending_machine',
'name': device['name'],
'location': {
'lat': device['lat'],
'lng': device['lng']
}
}
)
if response.status_code == 201:
data = response.json()['data']
print(f"Registered {device['id']}: {data['embed_url']}")
else:
print(f"Failed to register {device['id']}: {response.text}")
# Get all devices
response = requests.get(f'{API_BASE}/devices', headers=headers)
devices = response.json()['data']['devices']
print(f"Total devices: {len(devices)}")examples.reactTitle
examples.reactDesc
examples.npmInstall
terminal
npm install @trillboards/ads-sdkApp.tsx
App.tsx
import { TrillboardsProvider, TrillboardsAdSlot, useTrillboardsAds } from '@trillboards/ads-sdk/react';
function App() {
return (
<TrillboardsProvider config={{ deviceId: 'DEVICE_FINGERPRINT' }}>
<KioskScreen />
</TrillboardsProvider>
);
}
function KioskScreen() {
const { state, isReady, sdk } = useTrillboardsAds();
if (!isReady) return <div className="loading">Initializing...</div>;
return (
<div style={{ width: '100vw', height: '100vh' }}>
<TrillboardsAdSlot style={{ width: '100%', height: '100%' }} />
{state.isOffline && <div className="offline-banner">Playing cached ads</div>}
</div>
);
}examples.serverTitle
examples.serverDesc
server.ts
server.ts
import { PartnerClient } from '@trillboards/ads-sdk/server';
const client = new PartnerClient({ apiKey: 'trb_partner_xxxxx' });
// Get live audience for a screen
const audience = await client.audience.getLive('screen-123');
console.log('Viewers:', audience.face_count);
console.log('Attention:', audience.attention_score);
console.log('Mood:', audience.mood);
// Get earnings data
const earnings = await client.analytics.getEarnings();
earnings.forEach(e => {
console.log(e.screenId, e.period, '$' + (e.earner_revenue / 100).toFixed(2));
});
// Build a VAST tag for a specific screen
const vastBuilder = client.createVastTagBuilder();
const tag = await vastBuilder.buildTag({
deviceId: 'screen-123',
width: 1920,
height: 1080,
orientation: 'landscape'
});
console.log('VAST URL:', tag.url);
// Batch-report impressions
const tracker = client.createTrackingBatch();
const result = await tracker.report([
{
adId: 'ad-001',
impressionId: 'imp-001',
deviceId: 'dev-001',
duration: 15,
completed: true,
timestamp: new Date().toISOString()
}
]);
console.log('Tracked:', result.tracked, 'impressions');examples.proofTitle
examples.proofDesc
examples.nodeVerification
verify-proof.js
const crypto = require('crypto');
// Public key from api.trillboards.com/v1/advertiser/proof/.well-known/public-key
const PUBLIC_KEY_HEX = 'a5a9c43785680c62ebcd01ec49a0d6055fd140023df0f142c3e37c6abafce34f';
function verifyProof(proof) {
// Build payload from proof data
const timestamp = typeof proof.timestamp === 'object'
? proof.timestamp.getTime()
: proof.timestamp;
const payload = `v2.${timestamp}.${proof.adId}.${proof.impressionId}.${proof.screenId}.${proof.deviceId}`;
// Parse signature (remove "ed25519=" prefix)
const signatureHex = proof.signature.replace('ed25519=', '');
const signatureBytes = Buffer.from(signatureHex, 'hex');
// Create public key object
const publicKey = crypto.createPublicKey({
key: Buffer.concat([
Buffer.from('302a300506032b6570032100', 'hex'), // Ed25519 SPKI prefix
Buffer.from(PUBLIC_KEY_HEX, 'hex')
]),
format: 'der',
type: 'spki'
});
// Verify signature
return crypto.verify(null, Buffer.from(payload), publicKey, signatureBytes);
}
// Example usage
const proof = {
impressionId: '507f1f77bcf86cd799439011',
adId: 'campaign-abc123',
screenId: 'screen-xyz789',
deviceId: 'device-456',
timestamp: 1705401600000,
signature: 'ed25519=a1b2c3d4e5f6...'
};
console.log('Valid:', verifyProof(proof)); // true or falseexamples.pythonVerification
verify_proof.py
from nacl.signing import VerifyKey
from nacl.exceptions import BadSignature
# Public key from API
PUBLIC_KEY_HEX = 'a5a9c43785680c62ebcd01ec49a0d6055fd140023df0f142c3e37c6abafce34f'
def verify_proof(proof):
"""Verify an Ed25519 signed proof of play."""
verify_key = VerifyKey(bytes.fromhex(PUBLIC_KEY_HEX))
# Build payload
payload = f"v2.{proof['timestamp']}.{proof['adId']}.{proof['impressionId']}.{proof['screenId']}.{proof['deviceId']}"
# Parse signature
signature_hex = proof['signature'].replace('ed25519=', '')
signature_bytes = bytes.fromhex(signature_hex)
try:
verify_key.verify(payload.encode(), signature_bytes)
return True
except BadSignature:
return False
# Example usage
proof = {
'impressionId': '507f1f77bcf86cd799439011',
'adId': 'campaign-abc123',
'screenId': 'screen-xyz789',
'deviceId': 'device-456',
'timestamp': 1705401600000,
'signature': 'ed25519=a1b2c3d4e5f6...'
}
print(f"Valid: {verify_proof(proof)}")examples.curlVerification
examples.curlVerificationDesc
terminal
# No authentication required - public endpoint
curl -X POST https://api.trillboards.com/v1/advertiser/proof/verify-proof \
-H "Content-Type: application/json" \
-d '{
"signature": "ed25519=a1b2c3d4e5f6...",
"timestamp": 1705401600000,
"adId": "campaign-abc123",
"impressionId": "507f1f77bcf86cd799439011",
"screenId": "screen-xyz789",
"deviceId": "device-456"
}'
# Response:
# {
# "success": true,
# "verification": {
# "valid": true,
# "version": "v2"
# }
# }examples.proofTip examples.proofTipText /v1/advertiser/proof/.well-known/public-key examples.proofTipSuffix examples.proofTipLinkText examples.proofTipEnd