A support widget that already knows who it is talking to
- Published on
- • 4 mins read•--- views
A shop with its own help desk still has a gap: the shop and the desk do not know about each other. A customer with a question leaves the site, finds the desk separately, and opens a ticket as a stranger, retyping the name, email and phone the shop already has on file. The agent then spends two replies working out who they are talking to and which order this is about.
The vendor's answer is a snippet you paste into the theme. That closes half the gap and opens three new problems.
Problem one: the snippet is in the wrong place
Code in a theme template dies with the theme. A theme update, a switch to a child theme, a handover to a new developer, and tracking or support silently disappears. Nobody notices, because nothing errors. The widget simply is not there any more.
Worse, every setting is now a code edit. Colour, corner, language, which fields the form requires: all of them live in markup, so changing one means a deploy and a developer.
Move it into a plugin with a settings screen. This is unglamorous and it is the whole fix for this problem.
Problem two: identity has to be handed over
A widget that knows nothing produces a ticket that starts from zero. Passing the signed-in customer's details costs almost nothing:
$visitor = $user
? ['visitor_name' => $user->getName(),
'visitor_email' => $user->getEmail(),
'visitor_phone' => $user->getPhone()]
: [];
return [...$settings, ...$visitor];
Note the empty array. With no session the widget still renders, just without visitor data. This matters more than it sounds: an anonymous visitor is the one most likely to have a pre-sales question, and a widget that errors or hides for logged-out users removes support exactly where it is most valuable.
Problem three: a site-wide widget costs a query per page
Resolving the user and their meta on every render adds a database round trip to every page on the site, for a widget most visitors never open.
Cache it per user:
$cached = wp_cache_get('freescout_user_' . $user_id);
if ($cached) return unserialize($cached);
$dto = $this->loadFromDb($user_id);
if ($dto) wp_cache_set('freescout_user_' . $user_id, serialize($dto));
return $dto;
The key must include the user id. A shared key is how one customer's name ends up in another customer's chat widget, and that is a data leak rather than a performance bug. Whenever you cache something derived from who is asking, the identity belongs in the key. This is the single easiest way to turn a caching change into an incident.
Placement is a choice, not a default
Footer injection puts the widget on every page. A shortcode puts it on one. Both are legitimate: a shop wants it everywhere, a documentation site might want it only on the contact page.
Support both, and let them share the same identity code. The temptation is to build the site-wide version first and add the shortcode later by copying the render function, at which point you have two widgets that drift apart.
What to check before calling it done
Open the shop signed in and confirm the form starts pre-filled. Then open it in a private window and confirm the widget still appears, empty and working. The second check is the one people skip, and it is the one that catches the null-user crash.
Then look at what the widget is actually sending. It ships whatever you handed it to a third-party service, and "whatever you handed it" tends to grow. A phone number is reasonable for a support desk. An order history, an internal customer id or a loyalty tier probably went in because it was available, not because support needed it.
Related
Projects

CactusPlay - Game shop run by a vetted contractor network
Before: CIS buyers compared PlayStation and Xbox prices by hand across scattered sellers, and every order had to be filled by the founders themselves. After: a shop with a live catalogue and a CRM any vetted contractor with Turkish payment details could join to take orders and earn on them. Result: 20,000+ positions grabbed hourly, price changes reaching the shop and Yandex.Market untouched, and fulfilment no longer capped by one pair of hands.
Posts
Pricing a catalogue against a store you do not control
Reselling means your prices depend on someone else changing theirs. Fetching on page load takes your shop down with theirs; a nightly export goes stale. The middle path is a cache with a pricing strategy behind it.
Make delivery a consequence of payment, not a task someone performs
Digital goods delivered by hand are limited by working hours and protected only by attention. Moving delivery onto order events changes what the system guarantees, and the API is what stops a second channel becoming a second implementation.
Taking payment through a gateway that belongs to another site
When the payment gateway is contracted to a different site, the buyer leaves and you have to learn the outcome somehow. The two ways this integration usually fails are an unauthenticated callback and two order records that drift apart.
Open for contract collaboration
I am available for contract-based collaboration. If you have an interesting project idea, schedule a call via Calendly.
Schedule a 30-min call