Beacon

Embedded support ticketing

Your customers don't report bugs. They report feelings.

"It's broken." No URL. No screenshot. No account. No browser.

So somebody on your team spends the next two days asking four questions before anyone can start fixing anything. Beacon captures all four answers the second they hit send.

See it running → What it takes to add One endpoint · one script tag · zero dependencies
portal.acme.com/billing/invoices

Invoices

Acme Ltd · signed in as Alice Chen

InvoiceCustomerIssuedAmount
INV-2043Northwind Traders12 Aug£4,280.00
INV-2044Contoso Ltd14 Aug£1,145.50
INV-2045Fabrikam17 Aug£12,600.00
INV-2046Globex Retail18 Aug£860.00
INV-2047Tailspin Toys19 Aug£3,015.25

Your page. Your session. Our panel, in a shadow root — the only thing it shares with the page around it is a rectangle.

The part nobody budgets for

Every vague ticket costs you the same four emails.

  1. "Which page were you on?"
  2. "Can you send a screenshot?"
  3. "What browser, and what were you signed in as?"
  4. "Can you try it again and tell me exactly what you clicked?"

Four emails. Two days of calendar time. Then work starts.

That is not a support problem. It is a data-collection problem, and you are solving it with people — the most expensive and least reliable way to collect four facts a browser already knows.

What arrives instead

The ticket lands with the answers already in it.

Not a form your customer has to fill in properly. Captured, at the moment they hit send, whether they thought of it or not.

What it takes to add

One endpoint. One script tag. Then never again.

Everything else is a settings screen.

Launcher shape, which tabs exist, your own CSS, which URLs it appears on, who gets emailed — all of it is server-side. Changing any of it never touches your codebase a second time, and never needs your deploy window.

  1. An endpoint that vouches for your user

    Authenticated by your own session. Returns a signed, 120-second, single-use assertion. About twenty lines. This is the only code you write.

  2. A script tag

    47 KB gzipped and unminified, zero dependencies, and everything past the launcher loads lazily. It does not block your render.

Worked examples

Six ways it actually gets wired up.

Every one of these is the shipped API, not an illustration of one. The options are the options boot() takes; the events are the events it dispatches; there is nothing here you will discover we do not have.

Three options, and the third is a call into the endpoint you built. Launcher shape, which tabs exist, theme, which URLs it appears on — none of that is here, because none of it is yours to redeploy for.

<!-- the entire client-side integration -->
<script type="module">
  import { boot } from "https://cdn.beacon.example/widget/v1/loader.js";

  boot({
    baseUrl:   "https://support.example.com/api/v1/widget",
    projectId: "prj_4f9c2a…",
    // your backend vouches; this page never sees the signing secret
    getAssertion: async () => {
      const r = await fetch("/portal/support-identity", { method: "POST" });
      return (await r.json()).assertion;
    },
  });
</script>

The URL is versioned and ours. You do not vendor a copy into your web root — a copy is a thing that gets left behind.

Before you put it in front of your customers

The five things you're actually worried about.

You are about to run somebody else's software inside your product, in front of your customers, with your name above it. These are fair questions and they get straight answers.

"It'll break my layout."

It renders in a shadow root with all: initial. Your CSS cannot reach it and its styles cannot escape. A global * { box-sizing: content-box } on your page is a non-event.

"It'll slow my page down."

Zero dependencies — no framework, no polyfills, no analytics of ours riding along inside your product. Everything past the launcher is lazy.

"Your outage becomes my incident."

If our API is unreachable the widget removes itself and says nothing. No thrown error, no console output, no layout shift.

The silence is deliberate: noise from an unfamiliar vendor is indistinguishable from your page breaking.

"Someone will impersonate a customer."

We never accept an identity from browser JavaScript — only a signed assertion your backend mints, valid 120 seconds, single-use.

Support history is visible across an organisation, so anything weaker would be a way to read all of it.

"It'll show my customers things it shouldn't."

Customers never see SLA data. Not a timer, not a badge, not an apology for lateness — the customer serialiser has no field able to carry one, and every notification template is scanned for the vocabulary in tests.

"We'll need it configured differently per customer."

Every appearance and behaviour setting is per project: launcher, tabs, your own CSS, theme, which URLs it appears on, who gets notified. One deployment, any number of tenants, none of them touching your code.

How it was built

Specified first. Then kept honest.

Every requirement numbered. Every acceptance criterion testable. Every decision written down with its reasoning — including the ones that were later reversed, and who reversed them.

339requirements, each with acceptance criteria
1,720tests across the service and the widget
0runtime dependencies in the widget
47 KBgzipped, unminified, all of it

The bad news, up front

Three things we won't pretend about.

You will find these in due diligence. Finding them yourself is worse than hearing them now, so here they are.

Nothing is ever deleted.

No retention expiry, no purge, no cascade delete. That is what makes the audit log trustworthy, and it also means a data-subject erasure request cannot be satisfied today. It is a deliberate trade and it needs your legal team's sign-off before you deploy, not after.

Your own CSS voids our accessibility guarantee.

Theme colours are contrast-checked when you save them. An arbitrary stylesheet cannot be — no save-time filter can prove one stays legible in every state. So a project on custom CSS carries no WCAG AA conformance from us, and the system says so at the moment you save it.

The performance numbers are untested.

There is no load test, no restore drill and no third-party accessibility audit. The queries are indexed and free of N+1 and there are tests proving it — but nobody has yet put this under real traffic, and we are not going to quote you a figure we have not measured.

See for yourself

Five tenants. Five configurations. One widget.

Each one a real project with its own signing secret. Open two of them and note that the page around the widget never changes — and that on Globex the widget is genuinely absent from checkout rather than hidden there.