Get Hedhog updates in your inbox
New releases, fresh recipes, and breaking changes — no spam.
sac Submodule
tickets
Owns the ticket lifecycle: creation from every channel, status transitions, assignment, SLA clock, satisfaction rating, and the public tracking page.
Source path: libraries/sac/src/ticket
Module file: ticket.module.ts
Introduction
Tickets are the centre of the module. Every entry point converges here: the public website form (`POST /sac/public/tickets`), an internal submission from another library, an agent opening a ticket in the admin panel, and the inbound channels (e-mail and WhatsApp).
The public tracking number lives in `protocol`, formatted `YYYYMM-NNNNNN` and generated by a Postgres default (`sac_next_protocol()`), not by application code — so two concurrent submissions can never collide on it. The sequence is global rather than per-month, so `202608-000004` means "the fourth ticket ever, created in August 2026".
The SLA clock is stored on the ticket itself (`first_response_due_at`, `resolution_due_at`, `paused_at`, `paused_ms`) and recomputed as the ticket moves. Moving to `waiting_customer` pauses it; a customer reply resumes it and shifts the deadlines by the elapsed pause, so time spent waiting on the customer never counts against the team.
`requester_email` is optional: a WhatsApp ticket has no e-mail, and the requester is identified by `requester_phone` instead. Duplicate submissions are collapsed through `dedupe_hash` within a configurable window, which is what makes a double-clicked contact form return the original protocol instead of opening a second ticket.
HTTP Endpoints
8 endpoints
Paginated ticket list with filters used by the list and board screens.
- Query
- page, pageSize, search, status, priority, category_id, assignee_user_id
Aggregate counters for the KPI row (open, overdue, unassigned, resolved).
Full ticket with requester, SLA state, category, channel and attachments.
- Params
- id (int)
Open a ticket from the admin panel, on the `admin` channel.
Body
| Field | Type | Required | Notes |
|---|---|---|---|
| requester_name | string | yes | — |
| requester_email | string (email) | no | Optional: a WhatsApp ticket is identified by phone instead |
| requester_phone | string | no | — |
| subject | string | yes | — |
| description | string | yes | — |
| category_id | int | no | — |
| priority | low | normal | high | urgent | no | — |
Move the ticket through the status machine, pausing or resuming the SLA clock as required.
- Params
- id (int)
Agent reply or internal note. A public reply is delivered through the ticket channel — e-mail, or WhatsApp when the channel is of kind whatsapp.
- Params
- id (int)
Body
| Field | Type | Required | Notes |
|---|---|---|---|
| body_html | string | no | Sanitized on write; plain `body` is derived from it |
| visibility | public | internal | no | An internal note is never delivered to the customer and never counts as the first response |
Website contact form. Honeypot, optional CAPTCHA and a kill switch guard it.
Requester tracking page. The e-mail must match the ticket, otherwise the protocol alone would let anyone enumerate other people tickets.
- Params
- protocol
- Query