This guide explains how external applications and tenants can integrate with the Central Tickets multi-tenant SaaS system. The system supports secure authentication redirects where external apps can authenticate users and seamlessly redirect them to tenant-specific ticket systems.
Best for: Web applications that need to authenticate users and redirect them to the ticket system
POST https://ticketsystem.flare99.com/api/auth/redirect/{tenant_slug}
Content-Type: application/json
{
"email": "user@example.com",
"name": "John Doe",
"redirect_url": "https://your-app.com/dashboard"
}
JavaScript/Node.js:
async function redirectToTickets(email, name, tenantSlug, redirectUrl) {
const response = await fetch(`https://ticketsystem.flare99.com/api/auth/redirect/${tenantSlug}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: email,
name: name,
redirect_url: redirectUrl
})
});
if (response.redirected) {
window.location.href = response.url;
}
}
PHP:
function redirectToTickets($email, $name, $tenantSlug, $redirectUrl) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://ticketsystem.flare99.com/api/auth/redirect/{$tenantSlug}");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'email' => $email,
'name' => $name,
'redirect_url' => $redirectUrl
]));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json'
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$response = curl_exec($ch);
$redirectUrl = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
curl_close($ch);
header("Location: {$redirectUrl}");
exit;
}
Python:
import requests
def redirect_to_tickets(email, name, tenant_slug, redirect_url):
response = requests.post(
f"https://ticketsystem.flare99.com/api/auth/redirect/{tenant_slug}",
json={
"email": email,
"name": name,
"redirect_url": redirect_url
},
allow_redirects=False
)
if response.status_code == 302:
redirect_url = response.headers.get('Location')
return redirect(redirect_url)
Best for: Applications that need full programmatic access to ticket management
Use tenant-specific API tokens:
Authorization: Bearer {tenant_api_token}
X-Tenant-API-Token: {tenant_api_token}
# Tickets
POST /api/tickets # Create ticket
GET /api/tickets # List tickets
GET /api/tickets/{id} # Get ticket details
PUT /api/tickets/{id} # Update ticket
DELETE /api/tickets/{id} # Delete ticket
# Categories
GET /api/categories # List categories
# Tenant Info
GET /api/tenant # Get tenant information
// Create a ticket
const response = await fetch('https://ticketsystem.flare99.com/api/tickets', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_TENANT_API_TOKEN',
'Content-Type': 'application/json'
},
body: JSON.stringify({
title: 'Support Request',
description: 'I need help with...',
priority: 'medium'
})
});
Best for: Applications that want direct user login without redirects
POST https://ticketsystem.flare99.com/api/jwt/login
Content-Type: application/json
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"tenant_slug": "your-tenant-slug"
}
Your application must generate a JWT token signed with your tenant's JWT secret:
// Node.js example
const jwt = require('jsonwebtoken');
function generateJWTToken(email, name, tenantSecret) {
const payload = {
email: email,
name: name,
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + (2 * 60) // 2 minutes
};
return jwt.sign(payload, tenantSecret, { algorithm: 'HS256' });
}
JavaScript (Browser):
async function loginToTickets(email, name, tenantSlug, tenantJwtSecret) {
// Generate JWT token
const token = generateJWTToken(email, name, tenantJwtSecret);
// Call login endpoint
const response = await fetch('https://ticketsystem.flare99.com/api/jwt/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
credentials: 'include', // Include cookies for session
body: JSON.stringify({
token: token,
tenant_slug: tenantSlug
})
});
const result = await response.json();
if (result.success) {
// User is now logged in
window.location.href = result.redirect_url;
} else {
console.error('Login failed:', result.error);
}
}
PHP (Server-side):
<?php
use Firebase\JWT\JWT;
function generateJWTToken($email, $name, $tenantSecret) {
$payload = [
'email' => $email,
'name' => $name,
'iat' => time(),
'exp' => time() + (2 * 60) // 2 minutes
];
return JWT::encode($payload, $tenantSecret, 'HS256');
}
function loginToTickets($email, $name, $tenantSlug, $tenantSecret) {
$token = generateJWTToken($email, $name, $tenantSecret);
$ch = curl_init('https://ticketsystem.flare99.com/api/jwt/login');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'token' => $token,
'tenant_slug' => $tenantSlug
]));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json'
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$result = json_decode($response, true);
if ($result['success']) {
// Redirect user to ticket system
header('Location: ' . $result['redirect_url']);
exit;
}
}
?>
https://ticketsystem.flare99.comtickets.yourcompany.com)# DNS Configuration
tickets.yourcompany.com CNAME ticketsystem.flare99.com
# or
tickets.yourcompany.com A YOUR_SERVER_IP
Ensure HTTPS is configured for your tenant domain:
# Let's Encrypt example
certbot certonly --webroot -w /var/www/html -d tickets.yourcompany.com
Choose your integration method and implement the code in your application.
Use the provided test endpoints:
# Test redirect (returns JSON instead of redirect)
POST https://ticketsystem.flare99.com/api/test/auth/redirect/{tenant}
# Test pages
GET https://ticketsystem.flare99.com/test-auth-redirect
GET https://ticketsystem.flare99.com/test-auth-redirect-form
400 Bad Request: Invalid request data401 Unauthorized: Invalid API token403 Forbidden: Domain not verified for tenant404 Not Found: Tenant or endpoint not found429 Too Many Requests: Rate limit exceeded500 Internal Server Error: Server error{
"error": "Error message",
"code": "ERROR_CODE",
"details": "Additional information"
}
/integration endpoint/test-auth-redirectBest for: Laravel applications where you can modify Blade templates
<!DOCTYPE html>
<html>
<head>
<title>My Website</title>
<meta name="csrf-token" content="{{ csrf_token() }}">
</head>
<body>
<button onclick="contactSupport()">Contact Support</button>
<script>
// Pass user data from Laravel to JavaScript
const currentUser = @json([
'email' => auth()->user()->email ?? null,
'name' => auth()->user()->name ?? null,
'tenant_slug' => auth()->user()->tenant->slug ?? 'your-tenant-slug'
]);
function getCurrentUserEmail() {
return currentUser.email;
}
function getCurrentUserName() {
return currentUser.name;
}
function contactSupport() {
const email = getCurrentUserEmail();
const name = getCurrentUserName();
if (!email || !name) {
alert('Please log in to contact support');
return;
}
// Create form and submit
const form = document.createElement('form');
form.method = 'POST';
form.action = `https://ticketsystem.flare99.com/api/auth/redirect/${currentUser.tenant_slug}`;
const emailField = document.createElement('input');
emailField.type = 'hidden';
emailField.name = 'email';
emailField.value = email;
form.appendChild(emailField);
const nameField = document.createElement('input');
nameField.type = 'hidden';
nameField.value = name;
form.appendChild(nameField);
const redirectField = document.createElement('input');
redirectField.type = 'hidden';
redirectField.name = 'redirect_url';
redirectField.value = window.location.href; // Return to current page
form.appendChild(redirectField);
document.body.appendChild(form);
form.submit();
}
</script>
</body>
</html>
Best for: When you can't modify the main template structure
<!DOCTYPE html>
<html>
<head>
<title>My Website</title>
<meta name="user-email" content="{{ auth()->user()->email ?? '' }}">
<meta name="user-name" content="{{ auth()->user()->name ?? '' }}">
<meta name="tenant-slug" content="{{ auth()->user()->tenant->slug ?? 'your-tenant-slug' }}">
</head>
<body>
<button onclick="contactSupport()">Contact Support</button>
<script>
function getCurrentUserEmail() {
return document.querySelector('meta[name="user-email"]').getAttribute('content');
}
function getCurrentUserName() {
return document.querySelector('meta[name="user-name"]').getAttribute('content');
}
function contactSupport() {
const email = getCurrentUserEmail();
const name = getCurrentUserName();
const tenantSlug = document.querySelector('meta[name="tenant-slug"]').getAttribute('content');
if (!email || !name) {
alert('Please log in to contact support');
return;
}
// Create form and submit
const form = document.createElement('form');
form.method = 'POST';
form.action = `https://ticketsystem.flare99.com/api/auth/redirect/${tenantSlug}`;
const emailField = document.createElement('input');
emailField.type = 'hidden';
emailField.name = 'email';
emailField.value = email;
form.appendChild(emailField);
const nameField = document.createElement('input');
nameField.type = 'hidden';
nameField.name = 'name';
nameField.value = name;
form.appendChild(nameField);
const redirectField = document.createElement('input');
redirectField.type = 'hidden';
redirectField.name = 'redirect_url';
redirectField.value = window.location.href;
form.appendChild(redirectField);
document.body.appendChild(form);
form.submit();
}
</script>
</body>
</html>
Best for: Single Page Applications or when you need dynamic user data
<!DOCTYPE html>
<html>
<head>
<title>My Website</title>
</head>
<body>
<button onclick="contactSupport()">Contact Support</button>
<script>
async function getCurrentUserData() {
try {
const response = await fetch('/api/user/current', {
method: 'GET',
headers: {
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
'Content-Type': 'application/json'
}
});
if (response.ok) {
return await response.json();
} else {
throw new Error('Failed to get user data');
}
} catch (error) {
console.error('Error fetching user data:', error);
return null;
}
}
function getCurrentUserEmail() {
// This would need to be called asynchronously
// For synchronous access, use one of the other approaches
return null;
}
function getCurrentUserName() {
// This would need to be called asynchronously
return null;
}
async function contactSupport() {
const userData = await getCurrentUserData();
if (!userData || !userData.email || !userData.name) {
alert('Please log in to contact support');
return;
}
// Create form and submit
const form = document.createElement('form');
form.method = 'POST';
form.action = `https://ticketsystem.flare99.com/api/auth/redirect/${userData.tenant_slug}`;
const emailField = document.createElement('input');
emailField.type = 'hidden';
emailField.name = 'email';
emailField.value = userData.email;
form.appendChild(emailField);
const nameField = document.createElement('input');
nameField.type = 'hidden';
nameField.name = 'name';
nameField.value = userData.name;
form.appendChild(nameField);
const redirectField = document.createElement('input');
redirectField.type = 'hidden';
redirectField.name = 'redirect_url';
redirectField.value = window.location.href;
form.appendChild(redirectField);
document.body.appendChild(form);
form.submit();
}
</script>
</body>
</html>
Add this route to your routes/api.php:
Route::middleware('auth:sanctum')->get('/user/current', function (Request $request) {
return response()->json([
'email' => $request->user()->email,
'name' => $request->user()->name,
'tenant_slug' => $request->user()->tenant->slug ?? null
]);
});
// After user login on e-commerce site
async function handleSupportRequest(userEmail, userName) {
await redirectToTickets(
userEmail,
userName,
'your-store',
'https://yourstore.com/account/support'
);
}
// In user dashboard
public function contactSupport(Request $request) {
return redirectToTickets(
Auth::user()->email,
Auth::user()->name,
'your-saas',
route('dashboard')
);
}
// WordPress integration
add_action('wp_ajax_create_ticket', 'handle_ticket_creation');
function handle_ticket_creation() {
$user = wp_get_current_user();
redirectToTickets(
$user->user_email,
$user->display_name,
'your-wordpress-site',
home_url('/support/thanks')
);
}
https://ticketsystem.flare99.com/integrationThis integration guide is maintained with the codebase. Check for updates regularly.