Integration guide
This section covers integrating tirreno into your application.
Why send events to tirreno?
tirreno analyzes user events to detect threats and calculate risk scores. Use cases:
- Security monitoring: Detect account takeover, brute force attacks, suspicious behavior
- Threat hunting: Search for indicators of compromise across user activity
- Insider threats: Spot unusual employee behavior, potential data exfiltration
- Compliance: Activity logs and field audit trail for GDPR, SOC 2, PCI-DSS
- Forensics: Investigate incidents with full session history
- Risk scoring: Calculate user trust scores from behavior patterns
- Fraud prevention: Block malicious users before damage occurs
- IP enrichment: Add geolocation, ISP, VPN/proxy detection to IP data
tirreno tracks per-user metrics: devices per day, IPs per day, sessions, events per session.
IP Enrichment API: tirreno provides an API for IP geolocation and threat intelligence. The open-source Community Edition includes an optional IP enrichment pack (2,000 free API requests/month). For high-volume needs, contact tirreno for Enterprise options. See tirreno.com for pricing details.
Integration planning
Application Edition: For internal applications we recommend to use existing integrations. Check the list of available integrations or contact tirreno at team@tirreno.com for further details.
What to track
| Fraud Vector | How tirreno Detects | Events |
| Stolen credentials | Multiple IPs, unusual locations | account_login, account_login_fail |
| Account sharing | Concurrent sessions, device changes | page_view, account_login |
| Fake accounts | Disposable emails, VPN/proxy | account_registration |
| Data scraping | High volume, bot signatures | page_view, page_search |
| Privilege abuse | Off-hours, sensitive operations | account_edit, field_edit |
Where to integrate
Minimum:
- Login/logout
- Failed login attempts
- Registration
- Password/email changes
Recommended:
- Authenticated page views
- Search queries
- Data modifications
- File downloads/exports
- Admin actions
Data you need
| Data | Source | Required |
| User ID | Auth system | Yes |
| IP address | Request headers | Yes |
| URL | Request path | Yes |
| Timestamp | Server time (UTC) | Yes |
| Email | User profile | Recommended |
| User agent | Headers | Recommended |
Technical notes
Performance:
- Use async/non-blocking HTTP calls
- Set 3-5 second timeouts
- Queue events during high traffic
- Don't block user actions on tirreno response
Reliability:
- Implement retry with exponential backoff
- Fail open (allow user action if tirreno unavailable)
- Log failed events locally for debugging
Privacy:
- Never send passwords or tokens
- Hash sensitive IDs if needed
- Document data collection in privacy policy
- Consider GDPR requirements for EU users
Scalability:
- One event per significant user action
- Batch events where appropriate
- Monitor tirreno logbook for errors
Security considerations
When integrating tirreno, follow these security best practices:
- Install in private environment Deploy tirreno in a private network with controlled access
- Protect your API key Store in environment variables, never in code
- Use HTTPS Always send events over encrypted connections
- Don't log sensitive data Never include passwords, tokens, or PII in event payloads
- Fail open on errors Don't block users if tirreno is temporarily unavailable
- Set timeouts Use 3-5 second timeouts to prevent login delays
- Validate on your side tirreno is for monitoring, not input validation
- Send timestamps in UTC All
eventTime values must be in UTC. Configure your tirreno instance timezone during initial setup or in Settings → Time zone. The dashboard will display events in your configured timezone, but all data sent via the API must use UTC
Quick start
Important: tirreno must be integrated on the backend only. Never send events from frontend JavaScript or mobile apps. Client-side code can be inspected, modified, or bypassed entirely — attackers could disable tracking, forge events, or extract your API key. Backend integration ensures event data cannot be tampered with and your API credentials remain secure.
The fastest way to integrate tirreno is using an official tracker library.
cURL (raw API):
curl -X POST https://your-tirreno-instance.com/sensor/ \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "key=your-api-key" \
-d "userName=user123" \
-d "emailAddress=user@example.com" \
-d "ipAddress=192.168.1.100" \
-d "url=/login" \
-d "eventTime=2024-12-08 14:30:00.000" \
-d "eventType=page_view" |
PHP:
Requirements: cURL PHP extension
Installation:
composer require tirreno/tirreno-tracker |
Or manually via file download:
require_once("TirrenoTracker.php"); |
Usage:
<?php
// Load object
require_once("TirrenoTracker.php");
$tirrenoUrl = "https://example.tld/sensor/"; // Sensor URL
$trackingId = "XXX"; // Tracking ID
// Create object
$tracker = new TirrenoTracker($tirrenoUrl, $trackingId);
// Override defaults of required params
$tracker->setUserName("johndoe42")
->setIpAddress("1.1.1.1")
->setUrl("/login")
->setUserAgent("Mozilla/5.0 (X11; Linux x86_64)")
->setEventTypeAccountLogin();
// Set optional params
$tracker->setFirstName("John")
->setBrowserLanguage("fr-FR,fr;q=0.9")
->setHttpMethod("POST");
// Track event
$tracker->track(); |
Python:
pip install tirreno_tracker
from tirreno_tracker import Tracker
tracker = Tracker('https://your-tirreno-instance.com', 'your-api-key')
# Track a login
event = tracker.create_event()
event.set_user_name(user_id) \
.set_email_address(user_email) \
.set_ip_address(ip_address) \
.set_url(url_path) \
.set_user_agent(user_agent) \
.set_event_type_account_login()
tracker.track(event) |
Node.js:
npm install @tirreno/tirreno-tracker
const Tracker = require('@tirreno/tirreno-tracker');
const tracker = new Tracker('https://your-tirreno-instance.com', 'your-api-key');
// Track a registration
const event = tracker.createEvent();
event.setUserName(userId)
.setEmailAddress(userEmail)
.setIpAddress(ipAddress)
.setUrl(urlPath)
.setUserAgent(userAgent)
.setEventTypeAccountRegistration();
await tracker.track(event); |
Event tracking best practices
Which events to track
Essential events (always track):
| Event | When to Track | Why It Matters |
account_login | Successful authentication | Detect account takeover |
account_login_fail | Failed login attempts | Detect brute force attacks |
account_registration | New account creation | Detect fake account creation |
account_password_change | Password updates | Detect account compromise |
account_email_change | Email changes | Detect account hijacking |
Recommended events:
| Event | When to Track | Why It Matters |
page_view | Key page visits | Behavioral analysis |
page_edit | Content modifications | Detect malicious edits |
page_search | Search queries | Detect reconnaissance |
page_error | 4xx/5xx errors | Detect scanning/attacks |
field_edit | Data modification | Field audit trail |
Data quality guidelines
- Consistent user identifiers:
// Good - use permanent ID
$tracker->setUserName($user->id);
// Bad - don't use changing values
$tracker->setUserName($user->email); // Emails can change |
- Accurate timestamps:
The tracker libraries automatically set eventTime to the current UTC timestamp with milliseconds when you call track(). For manual timestamp handling, use the format Y-m-d H:i:s.v:
// PHP - include milliseconds
$eventTime = date('Y-m-d H:i:s.v'); // 2024-01-15 10:30:45.123 |
- Real IP addresses:
// Good - handle proxies correctly
function getRealIp(): string {
$headers = ['HTTP_CF_CONNECTING_IP', 'HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR'];
foreach ($headers as $header) {
if (!empty($_SERVER[$header])) {
$ips = explode(',', $_SERVER[$header]);
return trim($ips[0]);
}
}
return $_SERVER['REMOTE_ADDR'];
}
$tracker->setIpAddress(getRealIp()); |
- Complete user agent:
// Good - full user agent
$tracker->setUserAgent($_SERVER['HTTP_USER_AGENT']);
// Bad - truncated
$tracker->setUserAgent(substr($_SERVER['HTTP_USER_AGENT'], 0, 50)); |
Send all logged-in user events
Track page views and actions from authenticated users.
PHP:
session_start();
if (isset($_SESSION['user_id'])) {
$tracker->setUserName((string) $_SESSION['user_id'])
->setEmailAddress($_SESSION['user_email'])
->setIpAddress($_SERVER['REMOTE_ADDR'])
->setUrl($_SERVER['REQUEST_URI'])
->setUserAgent($_SERVER['HTTP_USER_AGENT'] ?? '')
->setHttpMethod($_SERVER['REQUEST_METHOD'])
->setEventTypePageView();
$tracker->track();
} |
Node.js:
if (userId) {
const event = tracker.createEvent();
event.setUserName(userId)
.setEmailAddress(userEmail)
.setIpAddress(ipAddress)
.setUrl(urlPath)
.setUserAgent(userAgent)
.setHttpMethod(httpMethod)
.setHttpCode(httpCode.toString())
.setEventTypePageView();
await tracker.track(event);
} |
Python:
if user_id:
event = tracker.create_event()
event.set_user_name(str(user_id)) \
.set_email_address(user_email) \
.set_ip_address(ip_address) \
.set_url(url_path) \
.set_user_agent(user_agent) \
.set_http_method(http_method) \
.set_http_code(str(http_code)) \
.set_event_type_page_view()
tracker.track(event) |
Protecting the registration
Protect your registration flow from fake accounts, bots, and abuse.
Track registration events
PHP:
$userId = createUser($_POST['email'], $_POST['password'], $_POST['name']);
$tracker->setUserName((string) $userId)
->setEmailAddress($_POST['email'])
->setFullName($_POST['name'])
->setIpAddress($_SERVER['REMOTE_ADDR'])
->setUrl('/register')
->setUserAgent($_SERVER['HTTP_USER_AGENT'] ?? '')
->setUserCreated(date('Y-m-d H:i:s'))
->setEventTypeAccountRegistration();
$tracker->track();
header('Location: /dashboard'); |
Python:
user_id = create_user(email, password, name)
event = tracker.create_event()
event.set_user_name(str(user_id)) \
.set_email_address(email) \
.set_full_name(name) \
.set_ip_address(ip_address) \
.set_url('/register') \
.set_user_agent(user_agent) \
.set_event_type_account_registration()
tracker.track(event) |
Node.js:
const userId = await createUser(email, password, name);
const event = tracker.createEvent();
event.setUserName(userId.toString())
.setEmailAddress(email)
.setFullName(name)
.setIpAddress(ipAddress)
.setUrl('/register')
.setUserAgent(userAgent)
.setEventTypeAccountRegistration();
await tracker.track(event); |
Protecting the login
Secure your login flow against brute force attacks and credential stuffing.
Track login events
PHP:
$email = $_POST['email'];
$password = $_POST['password'];
$user = authenticateUser($email, $password);
if (!$user) {
// Track failed login
$tracker->setUserName($email)
->setEmailAddress($email)
->setIpAddress($_SERVER['REMOTE_ADDR'])
->setUrl('/login')
->setUserAgent($_SERVER['HTTP_USER_AGENT'] ?? '')
->setEventTypeAccountLoginFail();
$tracker->track();
die('Invalid credentials');
}
// Track successful login
$tracker->setUserName((string) $user['id'])
->setEmailAddress($user['email'])
->setIpAddress($_SERVER['REMOTE_ADDR'])
->setUrl('/login')
->setUserAgent($_SERVER['HTTP_USER_AGENT'] ?? '')
->setEventTypeAccountLogin();
$tracker->track();
session_start();
$_SESSION['user_id'] = $user['id'];
header('Location: /dashboard'); |
Python:
user = authenticate_user(email, password)
if not user:
# Track failed login
event = tracker.create_event()
event.set_user_name(email) \
.set_email_address(email) \
.set_ip_address(ip_address) \
.set_url('/login') \
.set_user_agent(user_agent) \
.set_event_type_account_login_fail()
tracker.track(event)
# Return error
else:
# Track successful login
event = tracker.create_event()
event.set_user_name(str(user['id'])) \
.set_email_address(user['email']) \
.set_ip_address(ip_address) \
.set_url('/login') \
.set_user_agent(user_agent) \
.set_event_type_account_login()
tracker.track(event) |
Node.js:
const user = await authenticateUser(email, password);
if (!user) {
// Track failed login
const event = tracker.createEvent();
event.setUserName(email)
.setEmailAddress(email)
.setIpAddress(ipAddress)
.setUrl('/login')
.setUserAgent(userAgent)
.setEventTypeAccountLoginFail();
await tracker.track(event);
// Return error
} else {
// Track successful login
const event = tracker.createEvent();
event.setUserName(user.id.toString())
.setEmailAddress(user.email)
.setIpAddress(ipAddress)
.setUrl('/login')
.setUserAgent(userAgent)
.setEventTypeAccountLogin();
await tracker.track(event);
} |
Block blacklisted users
Check the blacklist API before allowing login:
$email = $_POST['email'];
$password = $_POST['password'];
// Block known attackers before authentication
if ($blacklistService->isBlacklisted($email)) {
die('Invalid credentials');
}
$user = authenticateUser($email, $password);
if (!$user) {
$tracker->setUserName($email)
->setEmailAddress($email)
->setIpAddress($_SERVER['REMOTE_ADDR'])
->setUrl('/login')
->setUserAgent($_SERVER['HTTP_USER_AGENT'] ?? '')
->setEventTypeAccountLoginFail();
$tracker->track();
die('Invalid credentials');
}
// Also check authenticated user
if ($blacklistService->isBlacklisted((string) $user['id'])) {
die('Invalid credentials');
}
// Track successful login
$tracker->setUserName((string) $user['id'])
->setEmailAddress($user['email'])
->setIpAddress($_SERVER['REMOTE_ADDR'])
->setUrl('/login')
->setUserAgent($_SERVER['HTTP_USER_AGENT'] ?? '')
->setEventTypeAccountLogin();
$tracker->track();
session_start();
$_SESSION['user_id'] = $user['id'];
header('Location: /dashboard'); |
Auto-ban abusive IPs
Use tirreno's IP analysis combined with the blacklist API for automatic protection.
Before implementing auto-ban, configure and test the threshold settings in tirreno:
- Go to Rules page in tirreno dashboard
- Set Manual review threshold (e.g., 33) users below this score appear in review queue
- Set Auto-blacklisting threshold (e.g., 20) users below this score are automatically blacklisted
- Click Update to save settings
Middleware for IP-based blocking
PHP:
$ip = $_SERVER['REMOTE_ADDR'];
if ($blacklistService->isBlacklisted($ip)) {
http_response_code(403);
die('Access denied');
} |
Python:
if blacklist_service.is_blacklisted(ip_address):
# Return 403 Access denied
pass |
Node.js:
if (await blacklistService.isBlacklisted(ipAddress)) {
// Return 403 Access denied
} |
Field audit trail
Track changes to important user fields for compliance, security, and regulatory requirements. The fieldHistory parameter allows you to send detailed change records.
Field history format
Each field change object has these properties:
| Property | Required | Type | Description |
field_id | Yes | int/string | Unique identifier for the field |
new_value | Yes | string | New value |
field_name | No | string | Human-readable field name |
old_value | No | string | Previous value |
parent_id | No | string | Parent record ID (for nested data) |
parent_name | No | string | Parent record name |
Note: Missing required fields default to "unknown". All values are converted to strings.
PHP:
function trackFieldChanges($userId, $userEmail, $oldData, $newData, $tracker) {
$trackableFields = [
'city' => 'User city',
'phone' => 'Phone number',
'address' => 'Address',
'company' => 'Company name',
];
$changes = [];
foreach ($trackableFields as $field => $fieldName) {
$oldValue = $oldData[$field] ?? '';
$newValue = $newData[$field] ?? '';
if ($oldValue !== $newValue) {
$changes[] = [
'field_id' => crc32($field),
'field_name' => $fieldName,
'old_value' => (string) $oldValue,
'new_value' => (string) $newValue,
'parent_id' => '',
'parent_name' => '',
];
}
}
if (!empty($changes)) {
$tracker->setUserName((string) $userId)
->setEmailAddress($userEmail)
->setIpAddress($_SERVER['REMOTE_ADDR'])
->setUrl($_SERVER['REQUEST_URI'])
->setUserAgent($_SERVER['HTTP_USER_AGENT'] ?? '')
->setEventTypeFieldEdit()
->setFieldHistory($changes);
$tracker->track();
}
}
// Usage
$oldData = getUserById($userId);
updateUser($userId, $_POST);
trackFieldChanges($userId, $userEmail, $oldData, $_POST, $tracker); |
Python:
def track_field_changes(user_id, user_email, old_data, new_data, tracker):
trackable_fields = {
'city': 'User city',
'phone': 'Phone number',
'address': 'Address',
'company': 'Company name',
}
changes = []
for field, field_name in trackable_fields.items():
old_value = old_data.get(field, '')
new_value = new_data.get(field, '')
if old_value != new_value:
changes.append({
'field_id': hash(field) & 0xffffffff,
'field_name': field_name,
'old_value': str(old_value),
'new_value': str(new_value),
'parent_id': '',
'parent_name': '',
})
if changes:
event = tracker.create_event()
event.set_user_name(str(user_id)) \
.set_email_address(user_email) \
.set_ip_address(ip_address) \
.set_url(url_path) \
.set_user_agent(user_agent) \
.set_event_type_field_edit() \
.set_field_history(changes)
tracker.track(event)
# Usage
old_data = get_user_by_id(user_id)
update_user(user_id, new_data)
track_field_changes(user_id, user_email, old_data, new_data, tracker) |
Node.js:
async function trackFieldChanges(userId, userEmail, oldData, newData, tracker) {
const trackableFields = {
city: 'User city',
phone: 'Phone number',
address: 'Address',
company: 'Company name',
};
const changes = [];
for (const [field, fieldName] of Object.entries(trackableFields)) {
const oldValue = oldData[field] ?? '';
const newValue = newData[field] ?? '';
if (oldValue !== newValue) {
changes.push({
field_id: hashCode(field),
field_name: fieldName,
old_value: String(oldValue),
new_value: String(newValue),
parent_id: '',
parent_name: ''
});
}
}
if (changes.length > 0) {
const event = tracker.createEvent();
event.setUserName(userId.toString())
.setEmailAddress(userEmail)
.setIpAddress(ipAddress)
.setUrl(urlPath)
.setUserAgent(userAgent)
.setEventTypeFieldEdit()
.setFieldHistory(changes);
await tracker.track(event);
}
}
// Usage
const oldData = await getUserById(userId);
await updateUser(userId, newData);
await trackFieldChanges(userId, userEmail, oldData, newData, tracker); |
Tracking nested/related data:
// For related records (e.g., user addresses)
$changes = [];
foreach ($updatedAddresses as $address) {
$original = $originalAddresses->find($address->id);
foreach (['street', 'city', 'zip'] as $field) {
if ($original->$field !== $address->$field) {
$changes[] = [
'field_id' => crc32($field),
'field_name' => ucfirst($field),
'old_value' => $original->$field,
'new_value' => $address->$field,
'parent_id' => (string) $address->id, // Link to address record
'parent_name' => "Address #{$address->id}", // Human-readable reference
];
}
}
} |
Testing your integration
Manual testing checklist
- Verify API connectivity:
curl -X POST https://your-tirreno.com/sensor/ \
-H "Api-Key: your-api-key" \
-d "userName=test-user-123" \
-d "emailAddress=test@example.com" \
-d "ipAddress=203.0.113.50" \
-d "url=/test" \
-d "userAgent=Mozilla/5.0 Test" \
-d "eventTime=2024-12-08 01:01:00.000" \
-d "eventType=page_view" |
- Check the Logbook:
- Log in to your tirreno instance
- Navigate to Logbook in the left menu
- View real-time API requests with Source IP, Timestamp, Endpoint, and Status
- Filter by endpoint, IP, or error messages using the search box
- The chart shows request volume over time to identify traffic patterns
- Check the Users page:
- Navigate to Users to see the tracked user
- Verify the user details and events are correctly recorded
- Verify event types:
- Test each event type you plan to use
- Confirm events appear in the correct user timeline
________________________________________________________________________________