Note : Please click on the “Save Changes” button
after uploading the title, logos, icons, and favicon.
Admin panel->App Settings->General Settings. There
you will find a section called "Set URL" with an input field of
"Terms of Service".
Admin panel->App Setting->General Settings. You can
update the "Primary and Secondary" colors of the application
according to your choices with this setting.
Admin panel->App Setting->General Settings. With
this feature, you can change the application's "Language and
Version" (LTR to RTL) according to your choices.
Note : Please click on the “Save Changes” button
after selecting the language.
Admin panel->App Setting->General Settings. You can
update the "Date and Time" formats of the application according
to your choices with this setting.
Admin panel->App Setting->General Settings.
Inactive customers/guests will receive an email after the time
period specified (Inactive Months) stating that their account
has been unused since then and will be deleted automatically
after the specified (Customer/guest Delete Days).
Admin Panel -> App Settings -> General Settings
page, the "Two Factor Authentication Settings" section allows
administrators to configure authentication options for employees
and customers.
Admin Panel -> App Settings -> General Settings
page, the "Security Settings" section offers administrators
control over specific security measures to safeguard the
application.
Admin Panel -> App Settings -> General Settings
page, the "Session Timeout Settings" section empowers
administrators to manage session durations for both admin panel
users and customers, enhancing security and privacy.
Admin panel->App Setting->General Settings.
Using the summer note editor, you can change the applications'
footer text. As shown in the below image,
Note : Please click on the “Save Changes” button
after customizing the footer text in the summer note.
Admin panel->App Setting->Ticket Settings. Please
select options to control the ticket settings. Or else, you can
also refer to the particular "Ticket Settings" page to get an
overview of the ticket settings, example as shown in the below
image.
Admin panel->App Setting->Ticket Settings. The
"superadmin" or any other “employee” who has permission to
access the admin panel can control the file size and file type
that will be uploaded to the tickets or articles by users.
Here, the superadmin or permitted employee can set the maximum files to be uploaded by users for tickets or articles. For example, if the "Max File Upload" option is set to 2 in the input fields by the admin, then the users can only upload a maximum of 2 images or files.
Here, the superadmin or permitted employee can set the maximum file size to be uploaded by users for tickets or articles. For example, if the file is set to 3MB by the admin, then the users can only upload images or files up to 3MB.
Here, the superadmin or permitted employee can set the file types to be uploaded by users for tickets or articles. For example, if the file type is set as .jpg or.png by the superadmin, then the users can only upload those types of images or files.
Note : Please click on the “Save Changes” button
after setting up your file settings.
Admin Panel->App Setting->SEO. This SEO setup comes
with three input fields. 1. The author, 2. The description, and
3. The keywords. Fill out those three input fields so that you
can increase the traffic for the application.
Admin Panel->App Setting->Google Analytics. Now
click on the toggle switch button of "Enable Google Analytics"
to enable and type the "Tracking ID" in the input field and
click on the "Save Changes" button to apply the changes.
Admin Panel->App Setting->Custom CSS and JavaScript
and follow the below given instructions.
Note : Please click on the “Save Changes” button after
setting up your custom CSS and JS. .
Admin Panel->App Setting->Captcha Setting page.
Note: Please use only "reCAPTCHA
v2" to generate "Site Key" and "Secret Key".
For example, if you want to use manual captcha in the above-mentioned forms, you must first activate the "Manual Captcha Enable" switch button from the above section, then enable the switch button of the forms where you want to use manual captcha.
Admin Panel->App Setting->Social Logins page and
follow the below process.
To use the above-mentioned social logins, for example, in my case, I chose the "Google" social login. Now go to the Google developer site and then you will need to configure and generate the "Client Id" and "Secret Id" for Google social login. Then, to show the Google social login on the application's login page, enter those two IDs into the input fields of the Google social login you generated, enable the switch, and click the "Save Changes" button.
Admin Panel->App Setting->Email Setting, and
finally access the Email Settings page. Follow the outlined
process to configure the settings accordingly.
Now please enter a valid email address in the input filed and click the "Send" button to test the "Send Test Mail" option.
Note: Please be aware that when
utilizing the "Sendmail" option, there is no requirement to
input values in the "Mail Username" and "Mail Password"
fields.
APIs & Services → OAuth consent screen.
APIs & Services → Credentials.
Click Create Credentials and select
OAuth Client ID.
https://yourapp.com/auth/microsoft/callback),
then click Register.
openidprofileemailoffline_accessMail.Send
IMAP (Internet Message Access Protocol) allows you to use a range of devices to access email stored on a server. Your hosting server must have an email plan that supports IMAP in order to use IMAP email.
To access the Email Settings, navigate to the Admin Panel > App Settings. Click on 'Email Settings' to proceed. Within the 'Email To Ticket' section, three settings are available:
To activate this feature, go to the Admin panel and navigate to App Settings. You will be redirected to the "Email Settings" page. Scroll down to the "IMAP Settings" section, where you'll find an "Add New" button.You can select Mail Driver from the given options called Gmail, Microsoft and Imap.
If you select IMAP- Fill in the required details accurately and save them. If the connection is successfully established, you have configured the IMAP Settings successfully.
If you choose Gmail as your mail driver, you must configure the Google API by obtaining a credentials JSON file from the Google Cloud Console and uploading it to your application. Follow the steps below to complete the setup:
APIs & Services → OAuth consent screen.
APIs & Services → Credentials,
then click Create
Credentials and select
OAuth Client ID.
https://yourapp.com/auth/google/callback).
If you choose Microsoft as your mail driver, you must configure an application in the Microsoft Azure Portal and connect it to your application using the generated credentials. Follow the steps below to complete the setup:
https://yourapp.com/auth/microsoft/callback).
openidprofile
emailoffline_access
Mail.Send
Mail.ReadWrite
The exciting part is that you can create multiple different IMAP configurations, each assigned to a specific category. For example, if you have a "Pre-Sales" category, you can link that category to a particular IMAP. Whenever customers send emails to the configured IMAP, it will automatically generate a ticket in the "Pre-Sale" category.
Email-To-Ticket feature is tried and tested with the following email service providers,
Example :To run the cron
command on your server, login to your cPanel server,
scroll down and navigate to Advanced->Cron Job, and
go to "Add New Cron Job" and select the "Once Per
Minute (*****)" option from the "Common Settings"
dropdown, and type the below command in the
"Command" input field, and click on the "Add New
Cron Job" button. If the process is done in the
correct way, then you’ll get a message that says
"cPanel successfully added the cron job".
Example Command: cd
/path-to-your-project && php artisan
schedule:run >> /dev/null 2>&1
NoteAutomatic functions of
the application will not work unless you run the
above cron command on your server.
Please follow this guide, or for reference video click here
Admin Panel->App Setting->External Chat page.
Now integrate the third-party external chat system code into the "External Chat" text area below. And click on the above switch buttons accordingly to enable or disable the third-party external chat system on the application.
Admin Panel->App Setting->Security Setting page and
follow the below process.
If you want to "Block" or "Allow" a country or multiple countries, then click and select the countries from the "Select Country" dropdown and click on the "Save changes" button to block or allow the countries you selected.
By default, all countries are allowed to use the application. If the "Select Country" dropdown is left empty, then any country can access the application's main site URL: https://app.spruko.com/mydesk.
If you want to "Block" or "Allow" a country or multiple countries, then click and select the countries from the "Select Country" dropdown and click on the "Save changes" button to block or allow the countries you selected.
By default, all countries are allowed to use the application. If the "Select Country" dropdown is left empty, then any country can access the application's admin URL:https://app.spruko.com/mydesk/admin.
Note: You are not allowed to block
your own country.
You have two input fields in the DoS attack setting. The first input indicates "Attempts" and the second input indicates "Seconds." For example, if 10 "attempts" in 30 "seconds" come from a particular IP address, then that IP address will be blocked or the user may be redirected to a captcha page, depending on the option you choose.
Example: First enable the toggle
switch button, choose the "View Captcha" option from the
"DOS Attack Setting" section and set 10 in the first input
field ("Attempts") and 30 in the second input field
("Seconds") and click on the "Save Changes" button. If a
user tries to access the application 10 times in 30 seconds,
his or her IP address will be temporarily blocked and he or
she will be redirected to the captcha page. Or if you choose
"Block IP Address", then it will block the IP address
permanently. If you don't want to use this feature, simply
click on the above toggle switch button to disable it.
You have to give the list of allowed domain's or list of blocked domain's by coma(,) saperated values. This will block or only allow the specified domain's
When you click on the "Add IP Address" button in the "IP List" table, a modal pop-up will appear with the "IP" input field and "Type". Fill in the input field and select the option from the "Type" and click on the "Save" button to apply the changes.


Here there are three more options. Edit, Delete, and Reset in the “Actions” column on the "IP List" table.
Note : Please click on the “Save”
button after you add or edit the IP addresses.

Admin Panel -> App Settings -> Bot Response Setting
page.
Admin Panel -> App Settings -> Twilio Settings
page.
Users can tailor these templates according to their preferences. The customized templates will be sent as ticket updates to customers.
Admin Panel -> App Settings -> Payment Gateways
page.To generate a PayPal client ID (key) and secret, follow these steps:
Go to the PayPal Developer Portal and log in with your PayPal account credentials.

To generate Stripe API keys (publishable and secret) for your application, follow these steps:
Visit the Stripe Dashboard and sign in with your credentials.




We have introduced the "AI Setup" page to enable AI in this application. For this, you need to generate a Gemini API key in Google AI Studio https://ai.google.dev/gemini-api/docs/api-key. Here, you can generate an API key by clicking on "Get a Gemini API key in Google AI Studio". This will ask you to log in using a Google account.
After logging in, you'll be redirected to https://aistudio.google.com/app/apikey. There, you will find a button "Create API Key". On clicking this, a modal will appear where you can select an existing project, or you can click on "Create API Key in new project".This will generate a new API key, which you can then use in the application.
Please enter the latest gemini model.
![]()
By enabling this setting, you can utilize the Gemini API key on the ticket view page. This allows users to either generate new text or improve existing text directly within the ticket interface. The Gemini API provides powerful language processing capabilities to assist in creating or enhancing content automatically based on the ticket context.
Single Sign-On (SSO) enables users to access multiple applications with one set of login credentials. This simplifies authentication, enhances security, and improves user experience.
Seamless sign-in to uhelp (secondary) if a user is already logged into the Main app.
Works across any domains/subdomains (no third-party cookies or iframes needed).
Minimal dependencies, easy to port to other stacks.
The SSO Contract (what uhelp expects)
class SsoController extends Controller
{
// Hardcoded config for demo/docs (move to env/DB in prod)
private const CLIENT_ID = 'UHELP';
private const SECRET = 'paste-a-long-random-secret-here';
private const ASSERTION_TTL = 60; // seconds
// Exact origins only (scheme://host[:port]) — no paths, no trailing slash
private const ALLOWED_RETURN_TO = [
'http://localhost',
// 'https://uhelp.example.com',
];
/**
* GET /sso/redirect?client_id=UHELP&return_to=<ABS uhelp>/admin/t/{tenant}/sso/consume&state=<nonce>
*/
public function redirect(Request $r)
{
$clientId = (string) ($r->query('client_id', self::CLIENT_ID));
$returnTo = (string) $r->query('return_to', '');
$state = (string) $r->query('state', '');
if (! $this->allowedReturnTo($returnTo)) {
abort(400, 'return_to not allowed');
}
// Require user to be logged into Main (first-party session)
if (! Auth::check()) {
return redirect()->guest(route('login', ['intended' => url()->full()]));
}
// Build short-lived HS256 assertion
$user = Auth::user();
$now = time();
$exp = $now + self::ASSERTION_TTL;
$claims = [
'sub' => (string) $user->getAuthIdentifier(),
'email' => (string) ($user->email ?? ''),
'name' => (string) ($user->name ?? ''),
'firstname' => (string) ($user->firstname ?? ''),
'lastname' => (string) ($user->lastname ?? ''),
'iat' => $now,
'exp' => $exp,
'jti' => (string) Str::uuid(),
'client_id' => $clientId,
];
$token = $this->sign($claims, self::SECRET);
// Top-level GET redirect back to uhelp (keeps uhelp's session cookie)
$qs = http_build_query(['assertion' => $token, 'state' => $state], '', '&', PHP_QUERY_RFC3986);
$sep = str_contains($returnTo, '?') ? '&' : '?';
return redirect()->away($returnTo.$sep.$qs);
}
/* ------------------ helpers ------------------ */
private function sign(array $payload, string $secret): string
{
$b64 = static function ($data): string {
if (is_array($data)) {
$data = json_encode($data, JSON_UNESCAPED_SLASHES);
}
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
};
$h = $b64(['alg' => 'HS256', 'typ' => 'SSO']);
$p = $b64($payload);
$s = $b64(hash_hmac('sha256', "$h.$p", $secret, true));
return "$h.$p.$s";
}
private function allowedReturnTo(?string $url): bool
{
if (! $url) return false;
$u = parse_url($url);
if (! $u || empty($u['scheme']) || empty($u['host'])) return false;
// only allow http/https schemes
$scheme = strtolower($u['scheme']);
if (!in_array($scheme, ['http', 'https'], true)) return false;
$origin = strtolower($scheme.'://'.$u['host'].(isset($u['port']) ? ':'.$u['port'] : ''));
return in_array($origin, array_map('strtolower', self::ALLOWED_RETURN_TO), true);
}
}
// server.mjs (run with: node server.mjs)
import express from 'express';
import crypto from 'crypto';
import { requireLogin } from './auth.js';
const app = express();
/** ---- Hardcoded config for demo (move to env/DB for prod) ---- */
const CLIENT_ID = 'UHELP';
const SECRET = 'paste-a-long-random-secret-here'; // must match uhelp’s verifier
const TTL_SECONDS = 60;
const ALLOWED_RETURN_TO = new Set([
'http://localhost', // exact origins only; no paths
// 'https://uhelp.example.com',
]);
/** ------------------------------------------------------------- */
const originOf = (u) => {
try { const x = new URL(u); return `${x.protocol}//${x.host}`; } catch { return null; }
};
const b64url = (buf) =>
buf.toString('base64').replace(/\+/g,'-').replace(/\//g,'_').replace(/=+$/,'');
/**
* GET /sso/redirect
* Query: client_id, return_to (ABS URL), state (nonce from uhelp)
*/
app.get('/sso/redirect', requireLogin, (req, res) => {
const clientId = (req.query.client_id || CLIENT_ID).toString();
const returnTo = (req.query.return_to || '').toString();
const state = (req.query.state || '').toString();
// 1) Validate return_to origin
const origin = originOf(returnTo);
if (!origin || !ALLOWED_RETURN_TO.has(origin)) {
return res.status(400).send('return_to not allowed');
}
// 2) Require user is logged in (handled by requireLogin)
const user = req.user; // { id, email, name, ... }
// 3) Build short-lived HS256 assertion
const now = Math.floor(Date.now() / 1000);
const claims = {
sub: String(user.id),
email: String(user.email || ''),
name: String(user.name || ''),
iat: now,
exp: now + TTL_SECONDS,
jti: crypto.randomUUID(),
client_id: clientId,
// optional extras to match your PHP: firstname/lastname, etc.
// firstname: user.firstName || '',
// lastname: user.lastName || '',
};
const header = { alg: 'HS256', typ: 'SSO' };
const h = b64url(Buffer.from(JSON.stringify(header)));
const p = b64url(Buffer.from(JSON.stringify(claims)));
const s = b64url(crypto.createHmac('sha256', SECRET).update(`${h}.${p}`).digest());
const token = `${h}.${p}.${s}`;
// 4) Top-level GET redirect back to uhelp
const sep = returnTo.includes('?') ? '&' : '?';
const location = `${returnTo}${sep}assertion=${encodeURIComponent(token)}&state=${encodeURIComponent(state)}`;
return res.redirect(location);
});
// Health
app.get('/', (_, res) => res.send('Main SSO up'));
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Main listening on http://localhost:${PORT}`));
# config/routes.rb
Rails.application.routes.draw do
get '/sso/redirect', to: 'sso#redirect'
end
# app/controllers/sso_controller.rb
class SsoController < ApplicationController
# 🔒 Hardcoded for docs/demo (move to ENV/DB in prod)
CLIENT_ID = 'UHELP'
SECRET = 'paste-a-long-random-secret-here' # must match uhelp
TTL = 60 # seconds
ALLOWED = ['http://localhost'] # exact origins only (scheme://host[:port])
# GET /sso/redirect?client_id=UHELP&return_to=<ABS uhelp>/admin/t/{tenant}/sso/consume&state=<nonce>
def redirect
client_id = (params[:client_id].presence || CLIENT_ID).to_s
return_to = (params[:return_to].presence || '').to_s
state = (params[:state].presence || '').to_s
unless allowed_return_to?(return_to)
render plain: 'return_to not allowed', status: :bad_request and return
end
# ✅ Require the user is logged in on Main
# Example with Devise:
unless user_signed_in?
redirect_to new_user_session_path(intended: request.original_url) and return
end
# Build short-lived HS256 assertion
now = Time.now.to_i
claims = {
sub: current_user.id.to_s,
email: current_user.email.to_s,
name: (current_user.respond_to?(:name) ? current_user.name.to_s : ''),
iat: now,
exp: now + TTL,
jti: SecureRandom.uuid,
client_id: client_id,
# optional extras if you like:
# firstname: current_user.first_name.to_s,
# lastname: current_user.last_name.to_s,
}
token = sign_hs256(claims, SECRET)
# Top-level GET back to uhelp
qs = Rack::Utils.build_query(assertion: token, state: state)
sep = return_to.include?('?') ? '&' : '?'
redirect_to "#{return_to}#{sep}#{qs}"
end
private
def allowed_return_to?(url)
uri = URI.parse(url) rescue nil
return false unless uri&.scheme && uri&.host
origin = "#{uri.scheme}://#{uri.host}"
origin << ":#{uri.port}" if uri.port && ![80,443].include?(uri.port)
ALLOWED.map!(&:downcase)
ALLOWED.include?(origin.downcase)
end
def sign_hs256(payload, secret)
header = { alg: 'HS256', typ: 'SSO' }
h = b64url(header.to_json)
p = b64url(payload.to_json)
s = b64url(OpenSSL::HMAC.digest('sha256', secret, "#{h}.#{p}"))
"#{h}.#{p}.#{s}"
end
def b64url(bytes_or_str)
s = bytes_or_str.is_a?(String) ? bytes_or_str : bytes_or_str.to_s
Base64.urlsafe_encode64(s).delete('=')
end
end
By enabling this setting, you can utilize the Gemini API key on the ticket view page. This allows users to either generate new text or improve existing text directly within the ticket interface. The Gemini API provides powerful language processing capabilities to assist in creating or enhancing content automatically based on the ticket context.