Admin Guide

Configure and manage every aspect of your Open Help Desk installation.

The admin panel is accessible from the sidebar when logged in as an Admin. All configuration in this guide is done through the admin panel unless a restart is required (only for environment variable changes).

Categories, Types & Items

Ticket classification follows a three-level hierarchy: Category → Type → Item. This model, sometimes called CTI (Classification-Type-Item), is borrowed from ITSM tooling like BMC Remedy. It lets you route tickets consistently based on what kind of request they are.

Hierarchy rules

Worked example

Hardware
  Laptop
    Won't power on
    Broken screen
    Keyboard issue
    Battery not charging
  Desktop
    Won't power on
    Monitor issue
  Printer
    Not printing
    Paper jam
    Low ink

Software
  Microsoft 365
    Outlook not loading
    Teams audio issue
    SharePoint access
  VPN
    Can't connect
    Slow connection
  Custom application

Access Requests
  New account
    Active Directory
    Email / Microsoft 365
    VPN
    Application access
  Permission change
  Account unlock

Facilities
  Building access
  Equipment request
  Ergonomics

Managing the hierarchy

Navigate to Admin → Categories. The page shows a tree of all Categories with their Types and Items.

How CTI affects staff scope

Groups are scoped to Category/Type pairs. A group assigned to Hardware (category only) sees all hardware tickets. A group assigned to Hardware / Laptop sees only laptop-type hardware tickets. See Groups & scope for the full explanation.

Users & roles

Manage accounts from Admin → Users. The page lists all accounts with their role, last login, and MFA status.

Roles

RoleCapabilities
Admin Full access to all settings, all tickets, and all user accounts. Can manage categories, groups, statuses, SLA policies, email templates, webhooks, and plugins. Always retains local-auth access even when SAML is enabled.
Staff Can view, reply to, assign, and update tickets within their group scope. Can look up any ticket by ID regardless of scope. Can leave internal notes not visible to users. Can resolve and close tickets. Cannot access admin settings.
User Can submit tickets and view their own tickets. Can add replies while the ticket is open. Can reopen a Resolved ticket by replying within the configured reopen window. Cannot see other users' tickets.

Changing a user's role

Click the user's name in the list and change the role dropdown. Role changes take effect on the user's next page load — their current session is re-evaluated on each request.

Creating a user

Click New user. Enter name, email, and password. Select the role. The account is created immediately. If email is configured, you can optionally send a welcome email with a password-reset link instead of setting a password manually.

Forcing a password reset

From a user's profile page, click Reset password. The user receives an email with a time-limited reset link (1 hour). Their current password remains valid until they use the link.

Disabling an account

Disabled accounts cannot log in and are not visible in assignment dropdowns. Disable rather than delete to preserve ticket history and attribution. From a user's profile: Disable account. Re-enable at any time.

Groups & scope

Staff visibility — which tickets a staff member sees in their queue — is controlled entirely by group membership. An admin always sees all tickets. A staff member not in any group sees no tickets (except by direct search by ID).

How scope is calculated

When a staff member loads their ticket queue, the system collects all CTI scope entries from all their groups, unions them, and returns tickets that match any of those entries.

Scope entries are Category/Type pairs:

Items never affect scope. A staff member scoped to Hardware / Laptop sees all laptop tickets regardless of which Item is selected.

Creating and configuring a group

  1. Go to Admin → Groups → New group.
  2. Give the group a name (e.g. "Tier 1 Support", "Networking").
  3. Under Scope, add one or more Category/Type pairs. Click Add scope entry, pick a Category, and optionally narrow to a Type.
  4. Under Members, add staff accounts.

Common configurations

SetupConfiguration
Single team, all tickets One group. Add all Categories (no Type). Add all staff as members.
Separate teams by category One group per team. Each group scoped to the categories that team handles. Assign staff to the relevant group(s).
Tiered support Tier 1 group scoped to all Categories. Tier 2 group scoped to specific Category/Type pairs for complex issues. Staff in Tier 2 also see escalated tickets because they can be assigned directly (assignment overrides scope visibility for the assignee).
Cross-functional specialists Staff can belong to multiple groups. A networking specialist is in "Tier 1" (full scope) and "Networking specialists" (scoped to Software / VPN). They see all tickets from Tier 1 scope plus all VPN-specific tickets.

Assignment vs. scope

Scope determines what a staff member sees in their queue. Assignment is separate — any staff member can be assigned any ticket, even if it's outside their queue scope. Once assigned, the ticket appears in the assignee's queue regardless of CTI.

This is intentional: you should never be unable to escalate a ticket to a specialist because of routing rules.

Ticket statuses

Manage statuses from Admin → Statuses. Statuses represent where a ticket is in its lifecycle.

System statuses

These are created automatically on first run and have special behavior. Their names and colors can be changed, but they cannot be deleted.

StatusSpecial behavior
NewThe initial status assigned to every newly created ticket.
ResolvedSetting this status starts the reopen window timer. Users can reopen the ticket by replying within this window. When set, staff are prompted to enter optional resolution notes.
ClosedAutomatically set by a background job when the reopen window expires. No further user-initiated updates are allowed. Staff and admins can still change the status manually.

Custom statuses

Create custom statuses to represent intermediate states meaningful to your workflow. Common additions:

Each custom status has:

Reopen window

Configure how many days after Resolved a user can reopen a ticket by replying. Set this from Admin → Settings → Ticket lifecycle → Reopen window (days). Set to 0 to disable user-initiated reopening entirely (staff and admins can still reopen manually).

SLA policies

SLA (Service Level Agreement) tracking is available when SLA_ENABLED=true. SLA policies define the maximum time that should pass before a ticket receives a first response and before it is resolved.

Policy fields

FieldDescription
NameDisplay name for the policy, e.g. "Critical — 1h response"
PriorityOptional. Apply this policy to tickets of this priority.
CategoryOptional. Apply this policy to tickets in this category.
Response timeMaximum hours from ticket creation until a staff reply is added.
Resolution timeMaximum hours from ticket creation until the ticket is marked Resolved.

Policy matching

When a ticket is created, the system selects an SLA policy by matching in this order:

  1. Priority + Category — most specific match
  2. Priority only
  3. Category only
  4. A default policy (a policy with no Priority and no Category)
  5. No SLA — if no match

SLA indicators in the ticket queue

The ticket list shows a visual indicator for each ticket's SLA status:

Staff can sort the ticket queue by SLA status to prioritize tickets at risk of breaching.

Pausing SLA timers

When a ticket is moved to the Pending status (waiting on the user), the SLA timer is paused. The timer resumes when the ticket moves out of Pending. This prevents SLA breaches for tickets that are legitimately waiting on a response from the submitter.

Email templates

Email notification templates are edited from Admin → Email → Templates. Templates use Go's text/template syntax.

Available templates

TemplateWhen sentRecipients
ticket_createdA ticket is submittedTicket submitter
ticket_assignedA ticket is assigned to a staff memberAssigned staff member
reply_addedA public reply is addedAll participants (submitter + staff who have replied)
ticket_resolvedTicket status changes to ResolvedTicket submitter
ticket_closedTicket auto-closes after the reopen windowTicket submitter
guest_ticket_createdA guest submits a ticketGuest (by email provided at submission)
guest_access_linkGuest requests a new access linkGuest (by provided email)

Template variables

{{ .Ticket.ID }}               -- ticket UUID
{{ .Ticket.Subject }}          -- ticket subject line
{{ .Ticket.Description }}      -- ticket description body
{{ .Ticket.Status }}           -- current status name
{{ .Ticket.Priority }}         -- priority name
{{ .Ticket.Category }}         -- category name
{{ .Ticket.Type }}             -- type name (may be empty)
{{ .Ticket.Item }}             -- item name (may be empty)
{{ .Ticket.TrackingNumber }}   -- tracking number (guest access)
{{ .Ticket.CreatedAt }}        -- creation timestamp (RFC 3339)

{{ .Submitter.Name }}          -- submitter's full name
{{ .Submitter.Email }}         -- submitter's email address

{{ .Assignee.Name }}           -- assignee's name (in assigned template)
{{ .Reply.Body }}              -- reply body (in reply_added template)
{{ .Reply.Author.Name }}       -- reply author's name
{{ .ResolutionNotes }}         -- resolution notes (in resolved template)

{{ .TicketURL }}               -- full URL to view the ticket in the app
{{ .AppName }}                 -- configured application name

Template example

Subject: [{{ .AppName }}] Reply on: {{ .Ticket.Subject }}

Hi {{ .Submitter.Name }},

{{ .Reply.Author.Name }} replied to your ticket:

---
{{ .Reply.Body }}
---

View the full ticket: {{ .TicketURL }}

Ticket #{{ .Ticket.TrackingNumber }}

Preview and test

Use the Preview button on any template page to see a rendered preview with sample data. Use Send test to deliver a test email to your own address.

Webhooks

Webhooks let you push ticket events to external systems in real time. Configure them from Admin → Webhooks → New webhook.

Configuration

FieldDescription
URLrequiredThe HTTPS endpoint that receives the POST requests.
SecretrecommendedA shared secret used to sign delivery payloads with HMAC-SHA256. Verify the signature server-side to reject spoofed requests.
EventsrequiredSelect which events trigger this webhook. You can subscribe to all events or specific ones.
EnabledToggle deliveries on/off without deleting the configuration.

Events

EventTriggered when
ticket.createdA new ticket is submitted (any method)
ticket.assignedThe assignee or assigned group changes
ticket.status_changedThe ticket status changes to anything
ticket.reply_addedA public reply is added to a ticket
ticket.note_addedAn internal note is added (staff-only)
ticket.resolvedStatus changes to Resolved specifically
ticket.closedStatus changes to Closed (auto or manual)
ticket.reopenedA Resolved or Closed ticket is reopened

Payload format

POST https://your-endpoint.example.com/webhook
Content-Type: application/json
X-OHD-Event: ticket.reply_added
X-OHD-Delivery: 01JQXYZ...
X-OHD-Signature: sha256=<hmac-hex>

{
  "event":       "ticket.reply_added",
  "delivery_id": "01JQXYZ...",
  "occurred_at": "2026-04-05T14:32:00Z",
  "ticket": {
    "id":       "01JQABC...",
    "subject":  "Laptop screen flickering",
    "status":   "In Progress",
    "priority": "High",
    "category": "Hardware",
    "type":     "Laptop",
    "item":     "Broken screen"
  },
  "actor": {
    "id":   "01JQDEF...",
    "name": "Alex Chen",
    "role": "staff"
  },
  "data": {
    "reply_id":    "01JQGHI...",
    "body":        "I've ordered a replacement screen, it arrives Thursday.",
    "is_internal": false
  }
}

Verifying the signature

The X-OHD-Signature header is sha256=<hex> where the hex is an HMAC-SHA256 of the raw request body, keyed with your webhook secret.

# Python
import hmac, hashlib

def verify_signature(secret: str, body: bytes, header: str) -> bool:
    expected = "sha256=" + hmac.new(
        secret.encode(), body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, header)

# Node.js
const crypto = require('crypto');
function verifySignature(secret, body, header) {
  const sig = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(body)
    .digest('hex');
  return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(header));
}

Retries and delivery history

If the target URL returns a non-2xx status or times out (5 second timeout), the delivery is retried with exponential backoff: 1s, 2s, 4s, 8s, 16s. After 5 total attempts, the delivery is marked failed.

All deliveries (successful and failed) are logged and visible under Admin → Webhooks → [webhook name] → Deliveries. Each entry shows the event type, HTTP status, response body (truncated), and timing. You can manually re-trigger a failed delivery from this page.

Installing plugins

Plugins extend Open Help Desk with custom behavior. They run as sandboxed WASM modules. Manage them from Admin → Plugins.

Installing from a file

  1. Click Install plugin.
  2. Upload the .wasm file.
  3. The system validates the plugin manifest embedded in the binary. If the manifest is missing or malformed, installation is rejected with a specific error.
  4. After successful validation, the plugin is listed as installed but disabled. Review its declared permissions (network hosts, capabilities) before enabling.
  5. Toggle the plugin to Enabled.

Installing from a URL

Click Install from URL and provide a direct URL to a .wasm file. The application downloads it, validates the manifest, and stores it locally. The URL is not called again after installation — it is not a live reference.

Plugin configuration

If a plugin declares a config_schema in its manifest, a configuration form appears on the plugin's detail page. Fill in the required fields (e.g. API keys, URLs). Configuration changes take effect immediately without a restart. Fields marked "secret": true are stored encrypted and are never returned in the admin UI after saving.

Enabling and disabling

Toggle a plugin on/off from the plugin list. Disabled plugins receive no events. The plugin binary remains installed and can be re-enabled at any time. No restart is required.

Uninstalling

Click Uninstall on the plugin's detail page. The WASM binary and all stored configuration are deleted. Custom fields or UI panels added by the plugin are removed from the ticket form. Ticket data that was set by the plugin (e.g. custom field values) is preserved in the database.

Error monitoring

The plugin list shows a count of errors per plugin over the last 24 hours. Click a plugin's name to see the error log. A plugin that errors on more than 50% of event deliveries over a 10-minute window is automatically disabled and an admin notification is generated. The threshold is configurable under Admin → Settings → Plugin error threshold.

For details on building plugins, see Plugin Development.

Branding

Customize the application's visual identity from Admin → Settings → Branding. Changes apply immediately with no restart required.

SettingDescription
Application nameShown in the browser title bar, the sidebar, and email subject lines. Default: "Open Help Desk".
LogoPNG or SVG file, maximum 2 MB. Displayed in the sidebar header and in the email template header. Recommended size: 200×50px or similar landscape ratio.
Primary colorHex color code used for buttons, links, active states, and status badges. Default: #2563eb.
Support emailDisplayed in the footer of the application and as the reply-to address on outbound notifications. Should match or alias your SMTP_FROM address.
Support URLOptional. A link to your organization's support portal or knowledge base, shown in email footers.
FaviconICO or PNG file for the browser tab icon.

Branding is CSS/asset-level customization only. Layout changes, custom page sections, and additional UI components are out of scope for the built-in branding feature — use the plugin UI panel system for that.

← Authentication Working with Tickets →