Security and CSP

Allowed domains, test conversations, what protects what, Content Security Policy, pinned installs with SRI, and privacy.

Allowed domains

Each widget has a list of domains it may run on, in its Security tab at Settings → Widgets.

  • Before you add any domain, the widget works on any https:// site. Ticketping records each site it loads on and lists it under Seen recently.
  • Add the domains you expect. Use an exact host like app.acme.com, or a wildcard like *.acme.com. A wildcard matches subdomains, not acme.com itself.
  • Turn on Only allow these domains before you go live. Other sites then get origin_not_allowed, and still show up under Seen recently so you notice a domain you forgot.

Only https:// sites are accepted, apart from localhost.

Localhost and test conversations

localhost, 127.0.0.1 and [::1] always work, over http or https, on any port, so the snippet works locally with no setup. You can turn this off with Allow localhost.

Conversations from localhost are test conversations:

  • They’re tagged TEST in the dashboard and [TEST] in Slack, and routed like real ones.
  • They’re left out of reports.
  • They’re deleted after 30 days.
  • They only show up in the widget on localhost and test domains, so your real users never see them in their history.

Mark a domain as Test (for example staging.acme.com) to get the same treatment there.

What protects what

ConcernProtection
Someone pretending to be one of your usersVerified identity: a token signed on your server with your identity secret. Turn on Require verified identity to refuse unsigned identify calls
Reading another user’s conversationsHistory across devices needs a verified identity. Anonymous conversations are tied to a random token stored in that browser
Copying your snippet onto another siteAllowed domains. The publishable key is public, so the domain list is what limits where it works
A stolen identity tokenEach token is single use and expires within 10 minutes
A leaked identity secretRotate it, then revoke the old one. Revoking takes effect at once
Turning on features from the browser consoleFeatures turned off in the dashboard are enforced by the server
Malicious content in messagesAgent and email messages are sanitized on the server and again in the widget. Uploaded files other than images are always downloaded, never shown inline
SpamRate limits per visitor and per IP

Allowed domains are checked with the browser’s Origin header. Scripts outside a browser can fake it, so treat the domain list as one layer, not the thing that protects your data. Verified identity is.

Content Security Policy

If your site sends a Content-Security-Policy header, add these sources:

DirectiveSourcesWhy
script-srchttps://widget.ticketping.comThe loader and the widget
connect-srchttps://api.ticketping.com wss://api.ticketping.com https://widget.ticketping.comThe API, the live connection, and emoji data
img-srchttps://media.giphy.com https://*.giphy.com blob: data: plus your attachment host (below)GIFs, avatars, image previews before upload
media-srchttps://media.giphy.com https://*.giphy.comGIFs play as muted videos
style-src'unsafe-inline'The widget’s styles (below)

For example:

bash
Content-Security-Policy:
  script-src 'self' https://widget.ticketping.com;
  connect-src 'self' https://api.ticketping.com wss://api.ticketping.com https://widget.ticketping.com;
  img-src 'self' https: blob: data:;
  media-src 'self' https://media.giphy.com https://*.giphy.com;
  style-src 'self' 'unsafe-inline'

If you turn off GIFs for the widget, you can drop the GIPHY sources.

Images in conversations. Attachments and avatars load from signed file-storage URLs whose host isn’t fixed yet. Until it is, img-src https: is the reliable choice. If your policy can’t allow that, attached images and some avatars won’t show; the conversation still works.

Styles. The widget renders inside a shadow root, so your page’s CSS doesn’t affect it and its CSS doesn’t leak into your page. Its styles are a <style> element inside that shadow root, which CSP treats like any inline style, so style-src needs 'unsafe-inline'. Nonces aren’t supported yet. The widget adds nothing to your page’s <head> apart from its script.

Pinned install with Subresource Integrity

The loader at /v2/loader.js always loads the latest 2.x release. If your policy requires Subresource Integrity, or you want to upgrade on your own schedule, load a specific version directly:

html
<script>
  window.Ticketping ||= (...args) => (Ticketping.q ||= []).push(args)
  Ticketping('init', { publishableKey: 'pk_...' })
</script>
<script
  src="https://widget.ticketping.com/v2/2.0.0/widget.js"
  integrity="sha384-..."
  crossorigin="anonymous"
  async
></script>
  • Replace 2.0.0 with the version you want. Versioned files never change.
  • Get the hash from https://widget.ticketping.com/v2/2.0.0/manifest.json. Its integrity object has a sha384-... hash for widget.js and every other file in that version.
  • Without the loader there’s no data-key, so call init yourself, as above.
  • The pinned bundle loads straight away instead of waiting for the browser to be idle.
  • Features that load on demand, like the emoji picker, come from the same versioned folder.

You’ll need to update the version and hash to get fixes. New releases are listed on npm; the CDN uses the same version numbers.

Privacy

What the widget stores in the browser

The widget uses localStorage, never cookies. It stores a random visitor token and, for identified users, a session. Keys start with tp: and your publishable key. logout and consent('denied') delete them.

Consent mode

If you need consent before storing anything, start the widget in pending mode and grant consent from your cookie banner:

js
Ticketping('init', { publishableKey: 'pk_...', consent: 'pending' })

// When the visitor accepts:
Ticketping('consent', 'granted')

While pending, the widget stores nothing and doesn’t contact Ticketping’s API. Ticketping('consent', 'denied') stops it and clears what it stored.

GIPHY

GIFs load straight from GIPHY’s servers in the visitor’s browser, so GIPHY sees the visitor’s IP address and browser details. GIF searches go through Ticketping’s servers, so GIPHY doesn’t see who searched. GIFs are off by default for new widgets. Mention GIPHY in your privacy policy if you turn them on. See GIFs and emoji.

What Ticketping keeps, and for how long

DataKept
Conversations that became ticketsWith the ticket, until you delete it
Anonymous conversations that never became tickets (for example, AI-only chats)12 months after the last activity
Anonymous visitors12 months after the last activity, along with their remaining non-ticket conversations
Test conversations (localhost and test domains)30 days after they started, including any ticket created from them
Ended sessions30 days
Uploads never sent in a message24 hours
Visitor detailsThe IP address isn’t stored on the visitor. The user agent is reduced to browser, OS and device type. Page context is kept with the conversation, under the same rules

Conversations are only created when the visitor sends a message, so opening the widget doesn’t create any data.

Copy prompt for your AI coding agent

prompt
Make this site's Content-Security-Policy work with the Ticketping chat widget (v2).

Docs index: https://ticketping.com/llms.txt
This page as Markdown: https://ticketping.com/docs/security.md

1. Find where this project sets its CSP (headers config, middleware, meta tag or framework option).
2. Add: script-src https://widget.ticketping.com; connect-src https://api.ticketping.com wss://api.ticketping.com https://widget.ticketping.com;
   img-src https: blob: data: (or at least https://media.giphy.com https://*.giphy.com); media-src https://media.giphy.com https://*.giphy.com;
   style-src 'unsafe-inline'. Keep the existing sources.
3. If the site needs consent before storage, start the widget with consent: 'pending' and call Ticketping('consent', 'granted')
   from the existing cookie banner's accept handler.