Ticket deflection with HelpCCMS
Most support tickets are questions that were already answered somewhere. The answer exists, it is written down, and the person asking never found it. Deflection is not a clever trick to stop people from writing to you; it is showing them what you already wrote, at the moment they are about to ask.
This page shows how to build that with help content you publish in HelpCCMS. Two requests, a number you decide on, and a form that still opens when the answer is not there. You write the interface; we hand you the content.
What sits where
Your support screen stays yours. It knows your product, your users and your tone, and it is the place where someone decides whether to write to you. HelpCCMS holds the published answers and returns them on request.
The whole integration is two endpoints:
GET /api/deploy/{collection}/search?q=... find what might answer this
GET /api/deploy/{collection}/{key} read one answer in fullBoth are public and read-only over https. There is no key to issue, no account to create and no SDK to install, so this works from your front-end as well as from your server.
Step 1: search while the question is being typed
The moment a user starts describing a problem, you have the best search term you are ever going to get: their own words.
const BASE = "https://www.helpccms.com/api/deploy";
const COLLECTION = "your-collection-id";
async function findAnswers(question) {
const url = `${BASE}/${COLLECTION}/search?q=${encodeURIComponent(question)}&limit=3`;
const response = await fetch(url);
if (!response.ok) return [];
return (await response.json()).results;
}A result looks like this. The numbers below are a real response from our own collection, so you can run the same request and compare:
{
"key": "trouble.empty-tooltip",
"title": "My tooltip is empty",
"short_desc": "Nearly always one of three things, and none of them is a broken connection.",
"matched": "title",
"snippet": "Nearly always one of three things, and none of them is a broken connection.",
"score": 0.99
}Search once the user has described the problem, not on every keystroke. One request when they pause is enough, and it keeps your screen calm.
Step 2: decide whether you have an answer at all
This is the step that makes deflection work, and it is the step most implementations skip.
Every result carries a score between 0 and 1. Higher scores mean a stronger lexical match, with title matches weighted most heavily. Low scores are not failures; they are the system telling you that nothing here really fits.
const MINIMUM = 0.5; // measure this, see below
const answers = await findAnswers(subject);
const good = answers.filter((a) => a.score >= MINIMUM);
if (good.length === 0) {
showTicketForm(); // no answer: do not make them read three wrong ones
} else {
showAnswers(good); // answer first, ticket form still one click away
}Measure your own minimum. Take twenty questions your users actually asked, run them through search, and look at where the good answers stop and the noise starts. That line depends on your content, not on ours, and it is the single number worth spending an afternoon on. Write it down with the date, and check it again when your content has grown.
Two rules that cost nothing and save a lot of goodwill:
- Never show a wrong answer to avoid a ticket. One irrelevant article teaches a user that your help is useless, and then they stop looking forever.
- Never hide the ticket form. Deflection that traps people is a complaint with a delay.
Step 3: show the answer
The search result gives you enough for a list: a title, a snippet, and the key. When someone opens one, fetch the content:
async function readAnswer(key) {
const response = await fetch(`${BASE}/${COLLECTION}/${encodeURIComponent(key)}`);
if (!response.ok) return null;
return response.json(); // { key, title, short_desc, html, text, updated_at }
}html is ready to drop into a panel. text is the same content without markup, for a plain surface or for handing to something else.
Responses carry an ETag and may be cached for five minutes, so a second reader costs you nothing.
Step 4: when they write anyway, carry what they saw
If the user still opens a ticket, attach the keys of the answers you showed:
{
"subject": "The tooltip on the billing page stays empty",
"shown": ["trouble.empty-tooltip", "delivery.help-keys"],
"opened": ["trouble.empty-tooltip"]
}Two things become possible at once. Your support agent sees which answer did not land, so they do not send the same page back.
And the loop closes on the content itself. A deflection that failed is not a lost ticket; it is a topic that matched the question and did not resolve it. That gives you a concrete editing queue, ordered by how often each topic appears in it, and every entry names the question it should have answered. Rewriting one of those is the cheapest support work there is.
Measuring what it is worth
Deflection has a number attached to it, and that is why it is easy to sell internally. Three events, all of them in your own application, are enough:
| Event | When |
|---|---|
help_search | a user described a problem and you searched |
help_answer_opened | they opened one of the results |
ticket_created | they submitted the form anyway |
Count one help_search per support attempt, not per request. Someone who rephrases three times is one person trying to get one thing done, and counting three searches against one ticket would hand you a 67 percent deflection rate for a support attempt that deflected nothing. Group the events by session or by the attempt they belong to, then your deflection rate is the share of attempts that end without ticket_created.
The gap between opened answers and tickets tells you something else again: content that gets read and still leaves the question open.
We do not measure this for you, and that is deliberate. All three events happen in your interface. We see the requests that reach our origin, which is neither all of them (published content is cached at the edge) nor the part that matters. A vendor dashboard that claims to know your deflection rate would be guessing about your product. Your analytics already knows.
It is also not a number we need. What you deflect runs your support operation; it has nothing to do with what you pay us. See below.
What this costs to run
Nothing per user. HelpCCMS charges for the seat that edits the content, and delivery is unmetered: a thousand readers and a million readers cost you the same.
Here the content is yours. You can export all of it as structured JSON at any time, use it in any interface, and show it to as many people as you want. We do not need to count your audience or your deflected tickets to price the product. That is why the measurement above belongs in your own analytics: it is a number for running your support, not for settling your bill.
That is the structural difference with a dedicated onboarding or support platform, where pricing often rises with the size of your audience. One content system can feed help, onboarding and support instead of maintaining the same knowledge across three separate tools: one content model, one place where a change is made, one publishing step. It also removes a quiet failure that costs more than a subscription, which is onboarding telling a user something that support contradicts.
The trade is real and worth stating plainly: you build the interface, and you design the flow either way.
What this does not do
- No stemming and no meaning. A word matches from the start of a word, so
publishfindspublished.publicationdoes not findpublish, and a question phrased entirely in synonyms of your content will miss. - Published content only. Drafts never appear in results, for the same reason they are never delivered. A topic you unpublish disappears from search within the cache window.
- One collection per request. Search covers the collection you name. It does not cross collections.
- A ceiling on size. One request searches up to 300 published topics. Above that the response says
"truncated": true, which is the point at which you want a real index rather than this.
Where to start
- Publish the ten answers your support team sends most often, each on its own key.
- Put the search call behind the subject field of your existing form.
- Set a minimum, measure it against twenty real questions, and adjust.
- Log the three events and look at the ratio after a month.
The full endpoint reference, with status codes, caching headers and the version policy, is on the API reference.