Peabody Compliance SDK
Integrate sub-second location integrity and hardware-backed device verification into your mobile and web applications.
Installation
The Peabody SDK for iOS is distributed as a Swift Package. You can add it to your project in Xcode by pointing to our release repository.
https://github.com/PeabodySecure/PeabodySDK-Release
Configuration
The Peabody SDK supports multiple authentication methods depending on your security architecture. Choose one of the following methods to initialize the SDK early in your app lifecycle (e.g., in AppDelegate or your main App struct).
Method 1: Hardcoded API Key
Use this method if you wish to bundle your API key directly within the application.
import PeabodySDK Peabody.configure(apiKey: "your_api_key_here")
Method 2: Client ID Handshake (Recommended)
Use this method to exchange a public Client ID for a temporary session token. This avoids embedding your sensitive API key in the binary. You can configure this via code or Info.plist.
// Option A: Configure via code Peabody.configure(clientId: "your_client_id_here") // Option B: Add 'PeabodyClientID' to your Info.plist // The SDK will automatically perform the handshake on the first verification.
Debug Logging
During development, you can enable verbose console logging to inspect hardware signals and network handshakes.
Peabody.enableDebugLogging()
Requesting Permissions
Peabody requires location permissions to perform jurisdictional checks. You can request "When In Use" or "Always" access using our helper methods.
Peabody.requestWhenInUsePermission { status in
print("Location status: \(status)")
}
Customer Tracking
To accurately track and log end-user (retail player) activity in your Peabody Dashboard, it is required to provide a unique User ID and Email during every verification call. This allows you to audit specific users and detect multi-accounting or high-risk repeat offenders.
- External ID: Your internal database ID for the user (e.g., Player UUID or Username).
- External Email: The end-user's registered email address.
// Swift (iOS) Example
Peabody.verifyLocation(externalId: "user_123", externalEmail: "player@example.com") { result in
// Handle result
}
Running Verification
To perform a check, call verifyLocation. This method automatically gathers GPS coordinates, IP intelligence, and device hardware signals. The response provides deep programmatic access to specific risk vectors.
Note: Always pass the current player's ID and Email to ensure your database logs are correctly associated with the correct individual.
// Swift (iOS)
Peabody.verifyLocation(
externalId: "player_uuid_99",
externalEmail: "player@domain.com"
) { result in
switch result {
case .success(let verdict):
// 1. Direct Verdict
if verdict.isCompliant {
print("Access Granted. Risk Score: \(verdict.score)")
}
// 2. Programmatic Integrity Flags
if verdict.isVPNOrProxyActive {
print("Threat: VPN or Proxy Detected")
}
if verdict.isScreenCaptured {
print("Threat: Screen Mirroring Active")
}
// 3. Location Metadata
print("Device City: \(verdict.city)")
print("IP Address: \(verdict.ipAddress)")
case .failure(let error):
print("Verification error: \(error.localizedDescription)")
}
}
Continuous Jurisdictional Monitoring
For applications requiring constant compliance (such as live betting or regulated gaming), Peabody supports a "Heartbeat" mode. This automatically re-verifies the user's location at a set interval while the app is active, handling app lifecycle events (foreground/background) efficiently.
// Start monitoring every 15 minutes (900 seconds)
Peabody.startMonitoring(
interval: 900,
externalId: "user_123",
externalEmail: "player@example.com"
) { result in
switch result {
case .success(let verdict):
if !verdict.isCompliant {
// Take action: e.g. pause the game session
print("Jurisdictional change detected: \(verdict.reason)")
}
case .failure(let error):
print("Monitoring error: \(error.localizedDescription)")
}
}
// Stop monitoring when no longer required
Peabody.stopMonitoring()
Hardware-Backed Integrity
The Peabody SDK utilizes Apple's App Attest service to prove that requests originate from a genuine, unmodified device. This is handled automatically during the verifyLocation flow.
- Cryptographic proof via Secure Enclave
- Automatic key rotation and registration
- Replay protection via stateless HMAC challenges
Advanced Network Security (SSL Pinning)
To prevent Man-in-the-Middle (MITM) attacks and credential intercept, the Peabody iOS SDK employs Public Key Pinning. Every connection to our compliance servers is validated against known, trusted keys. If a mismatch is detected, the SDK immediately terminates the connection and logs a security event to your dashboard.
Security Note: This is a zero-configuration feature. The SDK is pre-configured with current and backup pins to ensure high availability without manual intervention.
Android SDK
Integrate sub-second location integrity and Google Play Integrity verification into your Android applications.
Installation
The Peabody SDK for Android is distributed via Maven. To integrate it, add the dependency to your app-level build.gradle file and ensure your repository settings are configured.
Step 1: Add Dependency
// build.gradle
dependencies {
implementation 'com.peabodycompliance:sdk-android:1.0.0'
}
Step 2: Repository Configuration
Ensure mavenCentral() is included in your settings.gradle or project-level build.gradle. If you are using a private repository, add the following:
// settings.gradle
dependencyResolutionManagement {
repositories {
mavenCentral()
// Add custom repository if provided
}
}
Configuration
Initialize the SDK early in your application lifecycle, typically in your MainActivity or Application class. You will need your Peabody API Key and Google Cloud Project Number.
// Kotlin
Peabody.configureWithApiKey(
context = this,
apiKey = "your_api_key_here",
cloudProjectNumber = 350256575822L
)
Running Verification
Verify a user's location and device integrity by calling verifyLocation. The SDK handles permission checks, GPS acquisition, and Play Integrity token generation automatically.
Note: For proper database logging, you MUST pass the unique Player ID and Email.
// Kotlin
Peabody.verifyLocation(
externalId = "user_abc_123",
externalEmail = "player@email.com"
) { result ->
result.onSuccess { verdict ->
if (verdict.isCompliant) {
// Access Granted
Log.d("Peabody", "Verified: ${verdict.score}/100")
}
}.onFailure { error ->
// Handle network or permission errors
}
}
Play Console Linking
For hardware-backed integrity to function, you must link your Google Play Console to the Peabody Google Cloud Project. This allows our servers to cryptographically verify your app's tokens.
Required Action:
- Log into your Google Play Console.
- Navigate to Release > Setup > App integrity.
- Under Google Play Integrity API, click "Link a Google Cloud project".
- Choose "Enter a Google Cloud project number" and enter:
3503453475822.
JavaScript SDK (Web)
Secure your web applications and sweepstakes sites using our browser-based trust layer.
Installation
You can integrate the Peabody Web SDK using our hosted CDN for immediate updates, or download the source for self-hosting.
Option 1: CDN (Recommended)
Include the Peabody script directly in your HTML <head>. This ensures you always have the latest security definitions.
<script src="https://peabodycompliance.com/resources/js/peabody.min.js"></script>
Option 2: Self-Hosting & GitHub
For organizations requiring full control over assets, you can download the script from our GitHub repository and host it on your own servers.
Download from GitHub →Configuration & Authentication
Security Warning: Never set Peabody.config.apiKey in browser-side code. Your master API key is visible to anyone who opens DevTools and can be used to drain your credit balance from any server in the world. Always use a short-lived session token instead.
The JS SDK authenticates via a session token — a short-lived credential issued by Peabody's handshake endpoint. The flow is always the same: your backend calls Peabody's /handshake.php using your Client ID (found in your dashboard), receives a session token, and passes it to the browser. The browser then calls /verify.php with that token — which is how Peabody identifies your account and bills one credit per call.
🛡️ Enhanced Security: Session Binding
To prevent "Credential Re-use" (where one user's valid session token is used to perform a verification for a different user), you should bind the session token to a specific User ID and Email during the handshake. If bound, Peabody will reject any verification call where the payload identifiers do not match the session identifiers.
Your two credentials:
• API Key — master secret. Only for direct server-to-server calls. Never put this in a browser or mobile app.
• Client ID — used exclusively to call /handshake.php from your backend. Keep this server-side too.
The Handshake Call (with Binding)
When calling /handshake.php, provide the externalId and externalEmail of the user you are about to verify. This locks the token to that specific user.
POST https://peabodycompliance.com/handshake.php
Content-Type: application/json
{
"clientId": "your_client_id",
"bundleId": "your-domain.com",
"platform": "web",
"externalId": "user_12345",
"externalEmail": "player@email.com"
}
// Peabody responds:
{
"sessionToken": "a3f8c2d1e4f6...",
"expiresIn": 86400
}
There are two integration patterns depending on your stack:
Pattern 1 — Server-Side Rendering (SSR)
Your backend calls the Peabody handshake on every protected page load and injects the returned token directly into the HTML. The token is baked in before the browser sees it — no extra client-side round trip needed.
PHP
<?php
// Your backend calls Peabody's handshake to get a session token
// We pass the user's ID and Email to BIND the token to this specific user.
$response = file_get_contents('https://www.peabodycompliance.com/handshake.php', false,
stream_context_create(['http' => [
'method' => 'POST',
'header' => 'Content-Type: application/json',
'content' => json_encode([
'clientId' => 'your_client_id',
'bundleId' => 'your-domain.com',
'platform' => 'web',
'externalId' => $user->id,
'externalEmail' => $user->email
])
]])
);
$peabody = json_decode($response, true);
$sessionToken = $peabody['sessionToken'];
?>
<script src="https://www.peabodycompliance.com/resources/js/peabody.min.js"></script>
<script>
Peabody.config.sessionToken = '<?= htmlspecialchars($sessionToken, ENT_QUOTES) ?>';
Peabody.config.tokenRefreshEndpoint = '/your-token-refresh-endpoint';
</script>
Node / Express
const fetch = require('node-fetch');
app.get('/protected-page', requireAuth, async (req, res) => {
// Call Peabody handshake server-side
const peabody = await fetch('https://peabodycompliance.com/handshake.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
clientId: 'your_client_id',
bundleId: 'your-domain.com',
platform: 'web'
})
}).then(r => r.json());
// Inject the token into your rendered template
res.render('protected-page', { sessionToken: peabody.sessionToken });
});
// In your template (EJS, Handlebars, etc.):
// <script>
// Peabody.config.sessionToken = '<%= sessionToken %>';
// Peabody.config.tokenRefreshEndpoint = '/your-token-refresh-endpoint';
// </script>
Python / Flask
import requests
from flask import render_template
from functools import wraps
@app.route('/protected-page')
@login_required
def protected_page():
# Call Peabody handshake server-side
peabody = requests.post('https://peabodycompliance.com/handshake.php', json={
'clientId': 'your_client_id',
'bundleId': 'your-domain.com',
'platform': 'web'
}).json()
return render_template('protected_page.html',
session_token=peabody['sessionToken'])
# In your Jinja2 template:
# <script>
# Peabody.config.sessionToken = '{{ session_token }}';
# Peabody.config.tokenRefreshEndpoint = '/your-token-refresh-endpoint';
# </script>
Pattern 2 — Single-Page Apps (SPA / React / Vue / Angular)
For client-side SPAs there is no server-rendered page to inject into. Instead, create a small protected endpoint on your own backend that calls the Peabody handshake and returns the token to your frontend. Point tokenRefreshEndpoint at that same route so the SDK can renew automatically.
Your backend token endpoint (any language)
// Example: Express route — requires the user to be authenticated first
app.post('/api/peabody-token', requireAuth, async (req, res) => {
// Call Peabody handshake with your Client ID (server-side only)
const peabody = await fetch('https://peabodycompliance.com/handshake.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
clientId: 'your_client_id', // ← kept on your server, never sent to browser
bundleId: 'your-domain.com',
platform: 'web'
})
}).then(r => r.json());
res.json({ sessionToken: peabody.sessionToken });
});
Frontend (JavaScript / React / Vue)
// On app load, fetch a token from your own backend (user must be logged in)
async function initPeabody() {
const res = await fetch('/api/peabody-token', { credentials: 'include' });
const data = await res.json();
Peabody.config.sessionToken = data.sessionToken;
Peabody.config.tokenRefreshEndpoint = '/api/peabody-token'; // SDK auto-refreshes here on expiry
}
await initPeabody();
Automatic Token Refresh
When a token expires, the SDK automatically calls your tokenRefreshEndpoint, which in turn calls the Peabody handshake to issue a fresh token. The failed verification is then retried transparently — no action needed from your application code.
// Your refresh endpoint calls Peabody's handshake (same as above) // The SDK calls it automatically when it gets a 401 — then retries the verification. Peabody.config.tokenRefreshEndpoint = '/api/peabody-token'; // That's it. The SDK handles the rest silently.
Web Verification
Trigger a verification session when a user performs a high-value action. This will prompt the browser for location access and gather device telemetry.
Note: Always pass the current user's ID and Email so your dashboard logs are correctly attributed.
async function checkUser() {
const result = await Peabody.verifySession('user_123', 'player@email.com');
// Always check for errors first — expired tokens, network failures,
// and server errors all return { status: 'error' } rather than throwing.
if (result.status === 'error') {
console.error('Verification error:', result.reason);
// e.g. show a retry prompt or redirect to login
return;
}
if (result.compliant) {
console.log('Access granted. Risk score:', result.risk_score);
} else {
console.warn('Risk detected:', result.reason);
// e.g. block the action, show a compliance message
}
}
Mac Compliance Agent
Hardware-backed location verification for macOS desktop users — bridging the gap between browser GPS and physical truth.
Why the Mac Agent Exists
Desktop Macs have no GPS chip. When a browser calls navigator.geolocation, macOS falls back to IP-based geolocation — meaning your user in Florida may appear to be in New Jersey because their ISP routes traffic through a distant Point of Presence. The Peabody Mac Compliance Agent solves this by reading real WiFi access point data (BSSIDs + signal strengths) directly from the hardware and signing it with an ECDSA P-256 key, giving the server cryptographic proof of the device's physical location.
How It Works
The agent is a lightweight macOS menubar app (PeabodyServiceMac) that runs a local HTTP server on localhost:12180. When the Peabody JS SDK detects a Mac, it silently probes this port. If the agent is running, its hardware payload is included in the verification POST to your server. Your server then verifies the ECDSA signature and uses the WiFi triangulation data — via the Google Geolocation API — to resolve the user's true physical coordinates.
WiFi Triangulation
Scans all nearby access points (BSSID + RSSI). Top 5 by signal strength are sent to Google Geolocation API for 3–10 meter accuracy.
Hardware Fingerprint
Uses IOPlatformUUID to generate a persistent hardware device ID, enabling anti-multi-accounting detection across sessions.
OS-Level VPN Detection
Inspects the system routing table for utun, ppp, and tap interfaces — 100% certainty vs browser-based WebRTC heuristics.
SDK Configuration
Two new options are available in Peabody.config to control Mac agent behavior:
Peabody.config.sessionToken = 'your_session_token';
// Set true to REQUIRE the agent on macOS.
// If the agent is not running, verifySession() returns { status: 'download_required' }
// instead of falling back to browser GPS.
Peabody.config.requireMacAgent = true;
// Path to the DMG served from your own server.
Peabody.config.macAgentDownloadUrl = '/downloads/PeabodyService.dmg';
When requireMacAgent is false (the default), the agent is used opportunistically — if it's running, its data enhances the verification; if not, standard browser geolocation is used as a fallback. Set it to true only when you need the highest-assurance mode on macOS.
Handling the download_required Response
When the agent is required but not detected, verifySession() returns a special status instead of calling your server. Your UI should prompt the user to download and install the agent before retrying.
const result = await Peabody.verifySession('user_123', 'player@email.com');
if (result.status === 'download_required') {
// Show install instructions to the user
showInstallModal();
// Optionally trigger the DMG download automatically (once per session)
if (!sessionStorage.getItem('peabodyDmgDownloaded')) {
sessionStorage.setItem('peabodyDmgDownloaded', 'true');
const a = document.createElement('a');
a.href = result.downloadUrl; // uses Peabody.config.macAgentDownloadUrl
a.download = 'PeabodyService.dmg';
a.click();
}
return;
}
// All other statuses proceed normally
if (result.compliant) { /* access granted */ }
Distributing the Agent
No build step required. Peabody hosts a signed and Apple-notarized DMG on our servers. The SDK's default macAgentDownloadUrl already points to it — so if you use the CDN-hosted SDK, your users will automatically receive our verified installer with no configuration needed.
Hosted DMG
https://www.peabodycompliance.com/downloads/PeabodyService.dmg
This is the default value of Peabody.config.macAgentDownloadUrl. You can override it if you need to self-host the DMG on your own infrastructure, but for most integrations no change is needed:
// Default — uses Peabody's hosted, signed DMG. No action required. Peabody.config.macAgentDownloadUrl = 'https://www.peabodycompliance.com/downloads/PeabodyService.dmg'; // Override only if self-hosting on your own servers: // Peabody.config.macAgentDownloadUrl = 'https://your-domain.com/downloads/PeabodyService.dmg';
The Peabody-hosted DMG is signed with an Apple Developer ID certificate and notarized by Apple. Users can open it immediately without any macOS security warnings.
User Installation Steps
The agent is a one-time install. Once it is running, all future verifications happen silently in the background — the user will never see the install prompt again.
- The DMG downloads automatically. Open it from the Downloads folder.
- Drag PeabodyServiceMac into the Applications folder shown in the installer window.
- Open PeabodyServiceMac from Applications. Grant Location Services and Network access when prompted by macOS.
- A small icon appears in the macOS menu bar — the agent is now running. Return to the page to continue.
Auto-Start
The agent registers itself as a macOS Login Item after the first launch. It will start automatically whenever the user logs in — no manual action required on subsequent visits.
Server-Side Verification (verify.php)
When a Mac hardware payload is present, your server must verify the ECDSA P-256 signature before trusting any of the hardware signals. The Peabody verify.php reference implementation handles this automatically. Key behaviors when mac_hardware is present in the payload:
- ECDSA signature is verified against the included public key (DER/SPKI format). Request is rejected with HTTP 403 if verification fails.
- Top 5 nearby networks by RSSI strength are sent to the Google Geolocation API. The returned coordinates replace browser GPS as the authoritative location.
vpn_activefrom the OS routing table overrides browser WebRTC VPN detection.device_id(hardware UUID) is stored in the log, enabling cross-session device tracking and multi-accounting detection.- The connected access point BSSID is stored in
verification_logs.bssid. - Low-precision and IP distance discrepancy risk penalties are suppressed — desktop Macs have no GPS chip, so browser accuracy is inherently coarse, and ISP routing makes IP geolocation unreliable.
Agent Payload Schema
The raw JSON returned by http://localhost:12180 and forwarded to your server under mac_hardware:
{
"timestamp": 1711046400,
"device_id": "550e8400-e29b-41d4-a716-446655440000",
"connected_network": {
"ssid": "MyWiFiNetwork",
"bssid": "aa:bb:cc:dd:ee:ff",
"rssi": -42
},
"nearby_networks": [
{ "ssid": "MyWiFiNetwork", "bssid": "aa:bb:cc:dd:ee:ff", "rssi": -42 },
{ "ssid": "NeighborNet", "bssid": "11:22:33:44:55:66", "rssi": -71 }
],
"vpn_active": false,
"signature": "MEUCIQDa...", // ECDSA P-256 signature over sorted JSON
"public_key": "MFkwEwYH..." // DER/SPKI-encoded EC public key (base64)
}
Security Properties
- Replay protection: The
timestampfield is verified server-side; stale payloads are rejected. - Tamper detection: JSON keys are sorted before signing. Any modification to the payload (e.g., changing WiFi BSSIDs or VPN status) invalidates the signature.
- CORS enforcement: The local bridge only responds to requests originating from your registered domain.
- No network egress: The agent never contacts external servers. All data flows through the browser to your server, keeping the architecture simple and auditable.
SDK Response Structure
The verification call returns a comprehensive object containing all trust signals. Whether using the iOS or JavaScript SDK, the underlying data structure is consistent.
Response Object Reference
{
"status": "failure",
"compliant": false,
"risk_score": "100",
"risk_level": "Critical",
"reason": "Located in exclusion zone: Atlantic City Casino District; Proxy/VPN detected",
"external_id": "user_12345",
"device_integrity": {
"hardware_integrity": true,
"is_jailbroken": false,
"is_mock_location": false,
"is_screen_captured": false,
"is_vpn_active": true
},
"jurisdiction": {
"name": "New Jersey",
"code": "US-NJ",
"status": "active",
"resolved": true,
"in_us": true
},
"exclusion_zone": {
"active": true,
"name": "Atlantic City Casino District",
"type": "casino_floor"
},
"metadata": {
"lat": 39.3585,
"lon": -74.4358,
"ip": "149.102.244.98",
"city": "Atlantic City",
"state": "NJ",
"country": "United States"
},
"timestamp": "2026-03-08 15:03:50"
}