1. Home
  2. Docs
  3. Install the widget

Install the Pong Chat widget

One line before </body> on every page. Your public key is pre-filled in the desk under Settings → Chat widget → Install.

The snippet

<script src="https://js.pong.ee/YOUR_PUBLIC_KEY" async></script>

That is all: an 8 KB loader, loaded async, that never blocks rendering. Your public key is part of the address.

Options on the same tag

Set the look and behaviour with data-* attributes. They win over PongSetup and the workspace defaults; yes/no attributes take "true" or "false".

<script src="https://js.pong.ee/YOUR_PUBLIC_KEY"
  data-theme="auto" data-color="#16a34a" data-button-position="bl" async></script>
AttributeValuesSame as
data-idyour public key, only with the older call.pong.ee/pong.js addressPongID
data-languageen, rulanguage
data-themelight | dark | autotheme
data-color#hexcolors.buttonBg
data-button-style3d | round | tabbuttonStyle
data-button-positionbr | blbuttonPosition
data-button-texttextbuttonText
data-sizesmall | normal | largesize
data-title, data-subtitletexttitle, subtitle
data-chat-width, data-chat-heightpixelschatWidth, chatHeight
data-z-indexnumberzIndex
data-start-hiddentrue | falsestartHidden
data-group-idgroup idgroupId
data-client-idyour user idclientId
data-deferred-loadingtrue | falsedeferredLoading
data-inject-toelement idinjectTo
data-commandstrue | falsecommands
data-data-layerlayer name, or false for nonedataLayer
data-debugtrue | falsedebug
data-onloada function name, e.g. onPong or app.onPong— (see below)

Objects and functions — identity, the full colors set, callbacks — still go in PongSetup.

Run your code when Pong is ready

The simplest way to set things up: name your own function in data-onload. Once pong.js runs, it calls that function with the ready Pong API as its argument. The function can live anywhere on the page — if it is defined further down, it is called after the page has loaded. A dotted path such as app.onPong works too.

<script>
  function onPong(Pong) {
    Pong('on', 'message', function (m) { if (m.type === 'agent') Pong('openChat', true); });
  }
</script>
<script src="https://js.pong.ee/YOUR_PUBLIC_KEY" data-onload="onPong" async></script>

There is no inline code on the loader tag, so this works under a strict Content Security Policy.

Calling Pong(...) before it loads

To call Pong('identify', …) or other methods from anywhere on the page before pong.js has loaded, add the queue line first. Calls made before the loader runs are kept and run once it does.

<script>window.Pong = window.Pong || function () { (window.Pong.q = window.Pong.q || []).push(arguments); };</script>
<script src="https://js.pong.ee/YOUR_PUBLIC_KEY" async></script>

The older snippet

Older forms keep working; you don’t need to change them: https://call.pong.ee/pong.js (or any address) with the key in data-id, and snippets that set PongID (or ChatraID) and PongSetup in a script above the loader.

Single-page apps

Add the snippet once, in the HTML shell. Pong follows history.pushState, replaceState and popstate on its own, so page views in React, Vue, Next.js or Nuxt routers reach the visitor's history in the desk without extra code. If your router changes the URL some other way, report the page yourself:

Pong('pageView');

React / Next.js

// app/layout.tsx (Next.js App Router)
import Script from 'next/script';

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        {children}
        <Script src="https://js.pong.ee/YOUR_PUBLIC_KEY" strategy="afterInteractive" />
      </body>
    </html>
  );
}

Website builders

PlatformWhere to paste
WordPressA header/footer code plugin (e.g. WPCode) → Footer, or your theme’s footer.php before wp_footer()
ShopifyOnline Store → Themes → Edit code → theme.liquid, before </body>
WebflowSite settings → Custom code → Footer code
WixSettings → Custom code → Add code, place in Body – end, all pages
SquarespaceSettings → Advanced → Code injection → Footer
TildaSite settings → More → HTML code for the HEAD / end of BODY
FramerSite settings → General → Custom code → End of <body> tag

Google Tag Manager

Tags → New → Custom HTML, paste the same line, trigger All Pages, then Submit → Publish.

<script src="https://js.pong.ee/YOUR_PUBLIC_KEY" async></script>

To set PongSetup, PongIntegration or call Pong('identify', …) from the tag, put them above the loader in the same tag, after the queue line. Chat events reach your GTM triggers by themselves — see Events in Google Tag Manager.

Moving from Chatra

Pong’s loader understands Chatra’s API, so your existing code keeps working. In your current Chatra snippet, change one thing — the script address:

https://call.chatra.io/chatra.js  →  https://js.pong.ee/YOUR_PUBLIC_KEY

The key in the address wins over ChatraID, so that line can stay or go.

Everything else can stay: Chatra(...) calls (including ones made before the script loads), ChatraSetup, ChatraGroupID and ChatraIntegration are read as their Pong twins (Pong(...), PongSetup, PongGroupID, PongIntegration). window.Chatra points to Pong unless Chatra’s own widget is still on the page. When convenient, rename to the Pong names — nothing breaks either way.

Load later

To keep the first paint of a heavy page clean, ask the loader to wait for the load event and an idle moment:

<script src="https://js.pong.ee/YOUR_PUBLIC_KEY" data-deferred-loading="true" async></script>

Content Security Policy

If your site sends a CSP header, allow the loader and the chat frame:

script-src https://js.pong.ee https://chat.pong.ee https://call.pong.ee;
frame-src https://chat.pong.ee;

Check it works

  1. Open your site — the chat button appears in the bottom-right corner.
  2. Send a message from the widget.
  3. It arrives in the desk at app.pong.ee within a second.
The button does not show? Open the browser console: with data-debug="true" on the tag the loader logs unsupported calls and debug output there.

← OverviewJavaScript API →