Add live chat to your WordPress site
There's no plugin to install — Lunachat is a plain script tag. Add it to your site's head once and it runs on every page.
Setup
Step 1: Copy your embed code
In the Lunachat panel, open Websites, pick your site and copy the embed snippet. The public key inside it belongs to that one site.
HTML <script> window.lunachat = window.lunachat || function () { (lunachat.q = lunachat.q || []).push(arguments); }; lunachat('init', 'pk_live_YOUR_KEY'); </script> <script async src="https://cdn.lunachat.co/w.js"></script>Step 2: Check whether your theme is classic or block
If Appearance shows an Editor entry you are on a block theme (Twenty Twenty-Four and later), and those themes have NO
header.phpfile. If Appearance shows Customize, it is a classic theme. The first method below works on both.Step 3: Add it with a code snippet plugin
This is the durable route. Install a snippet plugin such as WPCode, create a snippet, choose HTML as the type, set the placement to the site-wide head and paste the code. It survives both theme updates and theme changes.
Step 4: Without a plugin:
wp_headin a child themeAdd the hook below to your child theme's
functions.php. It works on block themes too, becausewp_headis called by every theme. Never edit the parent theme directly, an update wipes it.HTML add_action('wp_head', function () { ?> <script> window.lunachat = window.lunachat || function () { (lunachat.q = lunachat.q || []).push(arguments); }; lunachat('init', 'pk_live_YOUR_KEY'); </script> <script async src="https://cdn.lunachat.co/w.js"></script> <?php });Step 5: Save and open your site
Reload the page and the chat bubble appears in the bottom right. If it doesn't, clear your caching plugin's cache first, then hard-refresh the browser.
Worth knowing
- Themes and builders like Elementor, Astra and GeneratePress have their own custom-code fields; those work too.
- The JavaScript combine and defer options in caching plugins can break the widget. Exclude
w.jsfrom combining in WP Rocket, LiteSpeed Cache and Autoptimize; it already loadsasync, so deferring buys you nothing. - If you run a cookie consent plugin, check which category the script lands in. Put it under marketing rather than strictly necessary and the chat box never opens until a visitor consents.
- On WooCommerce stores it appears on product, cart and checkout pages automatically, with nothing extra to configure.
- The widget is under 40 KB gzipped and loads
async, so it won't hurt your Core Web Vitals.
If you run WooCommerce
Once the widget is in, an agent already sees which page the visitor is on. WooCommerce can add two more things: what is in the cart, and who the person writing actually is.
Show the cart total to your agents
Fields you pass through set appear next to the visitor on the conversation screen. Someone with 240 dollars sitting in their cart does not deserve the same answer as someone browsing a 9 dollar item. These fields arrive from the page, so they count as UNVERIFIED: useful context for an agent, never written to the contact's identity.
add_action('wp_footer', function () {
if (!function_exists('WC') || !WC()->cart) {
return;
}
$data = array(
'cartTotal' => (float) WC()->cart->get_cart_contents_total(),
'cartItems' => WC()->cart->get_cart_contents_count(),
);
?>
<script>
lunachat('set', <?php echo wp_json_encode($data); ?>);
</script>
<?php
});Verify the logged-in customer
Unlike a cart total, a name and email can be verified. Your server signs a token with your secret, the widget sends it through identify, and the panel shows the person as verified. When the same customer writes from their phone the records merge. You generate the secret under the Setup tab of the site's settings, and it is shown only once.
function lunachat_token(array $claims, string $secret): string {
$b64 = fn($v) => rtrim(strtr(base64_encode($v), '+/', '-_'), '=');
$body = $b64(wp_json_encode(array('alg' => 'HS256', 'typ' => 'JWT')))
. '.' . $b64(wp_json_encode($claims));
return $body . '.' . $b64(hash_hmac('sha256', $body, $secret, true));
}
add_action('wp_footer', function () {
if (!is_user_logged_in()) {
return;
}
$user = wp_get_current_user();
$token = lunachat_token(array(
'sub' => (string) $user->ID,
'name' => $user->display_name,
'email' => $user->user_email,
'iat' => time(),
'exp' => time() + 3600,
), LUNACHAT_SECRET);
?>
<script>
lunachat('identify', <?php echo wp_json_encode($token); ?>);
</script>
<?php
});