Skip to content

⚠️ Recent Visits is in beta. All listings and data shown are for demonstration purposes only and do not represent real or verified information. Tour listings are based on content from partner agencies’ websites and have not yet been confirmed by the agencies.

Recent Visits

SDK security and privacy

What is public and what is secret, how sessions and the domain check protect your agency, Content-Security-Policy settings, what data the widget sends and keeps, and visitor disclosure.

On this page

This page describes how the widget protects your agency and your visitors, and what you must do on your side. It describes design and behaviour that exist; it is not a certification, and we make no claim of regulatory compliance.

Public and private#

ItemPublic?Notes
Public key (rvk_…)YesIt identifies your integration and sits in your page's HTML. It does not authenticate anyone and cannot read private data by itself.
Session tokenNo (per visitor, short-lived)Created when the chat opens. Opaque, stored only as a hash on the server, never written to a cookie or localStorage, and not shown in the page.
Anything secretNever in the widgetDo not put any secret, password or private API key in widget configuration. The widget needs none.
Bot challenge secretServer side onlyNothing of it is in your page or in the package. The human check runs in a frame from recent-visit.com, which holds the public site key.

If a public key is copied to another site it does not work there: the page's domain must match the domain registered for the key. If a key is exposed or misused, revoke it in the portal (Website widget → Revoke this key) and register again for a new one.

How access is enforced#

  • Domain check. A session is issued only to a page whose origin matches your registered domain (https, default port, the domain or any of its subdomains; localhost for Test). The check is repeated on every request, so changing the domain or switching the integration off takes effect immediately. A session is bound to the exact origin that opened it.
  • The agency comes from the key, never from the request. The browser cannot name an agency, and the request format rejects unknown fields. A tour can only be named if it is your published tour.
  • Tenant isolation. Retrieval for the assistant runs with your agency enforced inside the database query, so another agency's documents cannot enter the answer. The assistant has its own tools and instructions and cannot call the Recent Visits marketplace tools.
  • Features. The server decides which features (chat, tours, quote) your integration has; page options can only hide them.
  • Instructions in documents are data. Text in your Knowledge documents, in tours or in visitors' messages is treated as untrusted reference material. It cannot change the assistant's rules, and the assistant is told never to reveal its instructions or other agencies' information.
  • No cookies. Requests are sent with credentials: 'omit'. Responses can be read only by the registered page (CORS is limited to your origin, never *).
  • Output safety. Model output is rendered as plain text; links are made only from https addresses and Recent Visits paths, and open in a new tab with noopener noreferrer.

Sessions and caching#

  • A session token is valid for 30 minutes of inactivity and for at most 12 hours in total; an expired token can still be renewed for a further 5 minutes. The client renews it at about three quarters of its life.
  • The look returned by POST /config is cached on the server for about a minute per integration, so Studio changes appear within about a minute; sessions are always checked live.
  • The script file is served with Cache-Control: public, max-age=3600 and an open CORS header, so Subresource Integrity works on any site. The API itself never sends Access-Control-Allow-Origin: *.
  • Chat messages are limited to 2,000 characters and request bodies are size-limited before they are read.

Content Security Policy#

If your site sends a Content-Security-Policy header, allow:

DirectiveAllowWhy
script-srchttps://recent-visit.comTo load /widget/1/widget.js (or 'self' if you serve a copy yourself).
connect-srchttps://recent-visit.comFor the widget's API calls.
img-srcThe origin of any logo or avatar image you configureThe widget itself needs no image sources.
style-srcNothing for most sitesThe widget's styles live inside its Shadow DOM. A policy that blocks <style> elements in shadow roots needs 'unsafe-inline' for styles.

For local development with data-api-base, allow that origin instead. The widget shows no frames.

What data goes to Recent Visits#

  • When the page loads: a request for the look (POST /config) carrying your public key; the browser adds its Origin. No visitor data.
  • When the chat opens: a session request. No cookies are used.
  • In a chat: the messages the visitor types, and the page context you chose to share (pageContext.tourSlug, pageContext.title). The widget does not read or send anything else from your page; it does not scrape page content.
  • Visitor's name: optional, a display name only.
  • Quote form: name, email, message and optional phone, country, date, travellers and budget, sent only when the visitor submits it, with their consent.
  • IP address: used for rate limiting like any web service.
  • In the browser: by default the session and the visible chat text are kept in the tab's sessionStorage so a reload keeps the conversation. Use storage: 'memory' to keep nothing. Invitations store a few flags and timestamps (no text). The widget never uses cookies or localStorage for the session.

Chats and quote requests are saved in Recent Visits and are visible to your agency's team in the portal. Chats that do not become a quote request are deleted after 90 days; chats from the Test environment after 7 days.

Privacy and visitor disclosure#

  • The chat opens with a note that the conversation is saved and shared with your agency, and asks visitors not to type their email address or phone number. If one is typed anyway it is removed before it is stored or shown to anyone, and the visitor is told.
  • The only place the widget asks for an email or phone is the quote form, which has a consent checkbox ("I agree to share these details with <your agency>"). You see the email and phone only after the visitor accepts one of your quotes.
  • An AI generates the replies and can be wrong. By default the widget presents the assistant as AI (its default name is "AI Travel Assistant" and its subtitle "AI travel assistant"), so keep it identifiable as AI if you rename it. Your own policy should not present its answers as binding. A quote from your agency is what commits you.
  • Mention the chat in your own privacy policy, and use consent mechanisms your law requires. This guide does not replace legal advice. The widget does not set cookies, but your site's cookie rules still apply to anything else on your pages.

Limits and abuse protection#

Requests are rate limited per network address, per visitor session and per integration, and a daily message budget per integration is set by Recent Visits staff (smaller for Test). When a limit is reached the widget says how long to wait, using the real time until that limit resets (for example "Please try again in about 2 minutes"), keeps the visitor's message, and turns Send off until then. Limits fail closed: if the limiter itself cannot be reached, expensive actions are refused. The values can change and are not part of the public contract.

Human check. Before a session starts, Recent Visits may ask the visitor's browser to pass a Cloudflare Turnstile check (Managed mode: most visitors see nothing; only a suspicious browser sees a checkbox). The widget does this by itself, only when the server asks, in a small frame loaded from recent-visit.com; nothing is added to your page and you do not need a Cloudflare account or key. That frame can only be embedded by the domain registered for your key. Each check passes once: the server verifies it with Cloudflare, accepts it only for this purpose and for recent-visit.com, and never accepts the same token twice. If the check is blocked (for example by a strict content blocker) the widget shows its usual "try again later" message and no session starts.

Performance#

The widget is one script of roughly 50 KB (minified, before compression) with no dependencies, loaded with async, and does nothing visible until it is ready. At load it makes one small request for its look; a session starts only when the visitor opens the chat. While a visitor waits for a person it polls gently (every few seconds, slowing down when nothing changes, and pausing in a background tab).

Reporting a vulnerability#

Use the details on the Contact page and describe what you found and how to reproduce it. Do not include real visitor data.

Last checked against the product:

  • Agency integration guide · How the widget knows which agency it belongs to, how environments and drafts work, how quote requests reach your portal, and a go-live checklist.
  • SDK troubleshooting · Fixes for the widget not appearing, wrong branding or content, options not applying, network and permission errors, Next.js and mobile problems, and quote requests that do not show up.

Still stuck? Contact us and tell us what you tried.