Admin Guide

Configure and manage every aspect of your Go 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.

Groups in the CTI editor

Each expanded Category or Type row shows a Groups subsection — the groups currently handling tickets at that node. You can add or remove groups directly from here without navigating to the Groups page. See Groups & scope for how scope is calculated. Items do not have a group management section.

Custom fields in the CTI editor

Each expanded Category, Type, or Item row shows a Fields subsection where you assign custom fields to that node. See Custom fields for the full explanation.

Users & roles

Manage accounts from Admin → Users. The page lists all accounts with their role, login type, and join date. Click any row to open the user detail page.

Roles

RoleCapabilities
Admin Full access to all settings, all tickets, and all user accounts. Can manage categories, groups, statuses, tags, 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. Can add and remove tags on any ticket. 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.

User detail page

Click any user in the list to open their detail page. From there you can:

Creating a user

Click Add User from the Users list. Enter email, display name, role, and an initial password. The account is created immediately and appears in the list.

Login type column

The Login column in the users list shows how the account authenticates:

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.

Custom fields

Custom fields let admins attach structured data to tickets beyond the built-in fields (subject, description, priority, CTI). They are managed from Admin → Custom Fields.

Field definitions

A field definition is the global blueprint for a field. It has:

Assigning fields to CTI nodes

Fields are assigned from Admin → Categories. Expand a Category, Type, or Item row — each has a Fields subsection where you can assign any active field definition. Per assignment you configure:

The fields available on a ticket are the union of all fields assigned to its selected category, type, and item, ordered by scope level (category → type → item) then sort order.

Ticket detail page

Custom field values are displayed in the Custom Fields sidebar card on the ticket detail page. Staff and admins can click Edit to update any value at any time after the ticket is created. Regular users see values in read-only form.

Tags

Tags are free-form labels that staff attach to tickets to aid categorization, search, and filtering beyond the formal CTI hierarchy. They are managed from Admin → Tags.

How tags are created

Tags are created on first use — there is no separate "create tag" step. When a staff member adds a tag to a ticket and types a name that doesn't exist yet, the tag is created automatically. Tags are stored lowercase regardless of what was typed.

Autocomplete

As staff type in the tag field on a ticket, existing active tags matching the prefix are suggested. Staff can select a suggestion or press Enter to create and apply a new tag with whatever they've typed.

Deactivating a tag

If an inappropriate or misspelled tag is created, admins can deactivate it from Admin → Tags by clicking Deactivate. Deactivated tags:

Restoring a tag

Admins can restore a deactivated tag by clicking Restore on the Tags admin page. The tag becomes active again and reappears in autocomplete.

Tag rules at a glance

ActionWho can do it
Add a new tag (first use creates it)Staff, Admin
Add an existing active tagStaff, Admin
Remove a tag from a ticketStaff, Admin
Deactivate a tagAdmin only
Restore a deactivated tagAdmin only
Re-create a deactivated tag nameNot allowed — restore instead

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 deactivated or 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:

Deactivating and deleting custom statuses

Custom statuses can be deactivated — they are removed from ticket-creation dropdowns but existing tickets retain the status, and the status continues to appear in filters and reports. Deactivated statuses can be reactivated at any time.

A custom status can only be deleted permanently when zero tickets are in that status. The delete button appears only when the ticket count for that status is 0. Deleting is irreversible; deactivating is always the safer option.

Status change timeline

Every status transition is automatically recorded on the ticket. The ticket detail page shows a chronological timeline that interleaves replies and status events. Status events show the old and new status (with their colors), who made the change, and the timestamp. The initial status on ticket creation is also recorded.

Reopen window

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

Reopen target status

When a ticket is reopened, it is moved to the Reopen target status. Configure this from Admin → Settings → General → Ticket lifecycle → Reopen target status. The picker lists all active, non-system statuses — system statuses (Resolved, Closed) are excluded because transitioning a reopened ticket back into a terminal state would immediately re-trigger the close flow. If no target status is configured, the ticket is moved to the first active custom status alphabetically.

SLA policies

SLA (Service Level Agreement) tracking is an optional feature. Enable it from Admin → Settings → Features → SLA tracking. It can also be pre-enabled at server startup by setting the SLA_ENABLED=true environment variable.

Once the SLA tracking toggle is on, a SLA Policies management blade appears directly in the Features tab. Policies define the maximum time from ticket creation until a first response and until the ticket is resolved.

Policy fields

FieldDescription
NameDisplay name for the policy, e.g. "Critical — 1h response"
PriorityApply this policy to tickets of this priority: critical, high, medium, or low.
CategoryOptional. Restrict this policy to a specific category. Leave at "All categories" for a catch-all.
Response targetMinutes from ticket creation until a staff reply must be added.
Resolution targetMinutes from ticket creation until the ticket must be 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 Go 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
Site nameShown in the sidebar header and browser title when no logo is set. Default: "Go Help Desk".
Logo Upload a logo file from Admin → Settings → Branding → Upload logo. Accepted formats: PNG, JPG, GIF, SVG. Maximum 2 MB.
  • Target size: 320 × 64 px. Raster images (PNG, JPG, GIF) are proportionally scaled to fit within this bounding box and re-encoded as PNG. Images that already fit are kept at their original dimensions.
  • SVG safety: SVGs are validated as well-formed XML and scanned for disallowed content — <script> tags, event handlers (onclick, etc.), javascript: URIs, <foreignObject>, and expression(). Malformed or unsafe SVGs are rejected.
When set, the logo replaces the site name text in the sidebar. The file is served from GET /api/v1/logo with a 5-minute public cache header.

Branding covers site name and logo 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 →