---
title: "Web SDK – Relyo"
description: "Add Relyo to a website: script or ESM, support link, React/Next.js, options, masking, CSP and limitations."
lang: en
canonical: https://relyo.de/en/docs/web
---

# Websites

This is how you add co-browsing for support to a website. The web SDK runs in every current browser and needs no framework. It mirrors the tab as page structure (DOM, using [rrweb](https://www.rrweb.io/)), not as video: this saves bandwidth and keeps text sharp.

## Adding it with a coding agent

Copy this prompt into your coding agent (Claude Code, Cursor, Copilot …) and add your project key if you have one:

```text
Add Relyo co-browsing support to this website.
First read the guide https://relyo.de/.well-known/agent-skills/relyo-integrate/SKILL.md
and the web docs https://relyo.de/en/docs/web.md, then:

1. Load the SDK only when "Support" is clicked (section "Adding the SDK", variant "Load only on click"),
   relayUrl 'wss://relyo.de', projectKey: 'PROJECT_KEY' (leave it out if there is none).
2. Add an unobtrusive "Support" link to the footer that calls showCode().
3. Mask sensitive areas (account, payment, personal data) with the class relyo-redact or redactSelector.
4. Extend an existing Content Security Policy with https://relyo.de (script-src) and wss://relyo.de (connect-src).
5. Run lint, typecheck and tests and summarise what changed.
Never start a support session or enter support codes yourself.
```

## Adding the SDK

### With a script tag

```html
<script src="https://relyo.de/sdk/relyo.js"></script>
<script>
  const relyo = Relyo.init({ relayUrl: 'wss://relyo.de', projectKey: 'pk_yourproject' });
</script>
```

The script creates the global object `Relyo` (about 40 kB gzip). Call `Relyo.init` once per page; a second call returns the same instance.

Two addresses are available:

| Address                                | Behaviour                                                                     |
| -------------------------------------- | ----------------------------------------------------------------------------- |
| `https://relyo.de/sdk/relyo.js`        | always the latest version, browsers check for changes every time (`no-cache`) |
| `https://relyo.de/sdk/v0.1.0/relyo.js` | fixed version, cached for one year                                            |

### Load only on click

The leanest option: the SDK is only loaded when someone taps “Support”. Until then there is not a single request to relyo.de. This is how the support link on [jochen-maurer.com](https://jochen-maurer.com) works.

```html
<a href="mailto:support@example.com" id="support">Support</a>

<script>
  let relyo;
  function loadRelyo() {
    relyo ??= new Promise((resolve, reject) => {
      const script = document.createElement('script');
      script.src = 'https://relyo.de/sdk/relyo.js';
      script.onload = () =>
        resolve(
          Relyo.init({ relayUrl: 'wss://relyo.de', projectKey: 'pk_yourproject', connect: 'lazy' }),
        );
      script.onerror = () => {
        relyo = undefined;
        reject(new Error('Relyo not reachable'));
      };
      document.head.append(script);
    });
    return relyo;
  }

  document.querySelector('#support').addEventListener('click', (event) => {
    event.preventDefault();
    const fallback = event.currentTarget.href; // fallback if relyo.de cannot be reached
    loadRelyo()
      .then((r) => r.showCode())
      .catch(() => (location.href = fallback));
  });
</script>
```

### As a module

`relyo.mjs` is the same SDK as an ES module and can be loaded from any page:

```js
const { Relyo } = await import('https://relyo.de/sdk/relyo.mjs');
const relyo = Relyo.init({ relayUrl: 'wss://relyo.de', projectKey: 'pk_yourproject' });
```

There is no npm package yet.

### React and Next.js

The SDK needs `window` and `document`. In Next.js it therefore belongs in a client component and is only loaded on click, never during server rendering.

```tsx
'use client';

interface RelyoInstance {
  showCode(): void;
}
declare global {
  interface Window {
    Relyo?: { init(options: Record<string, unknown>): RelyoInstance };
  }
}

let relyo: Promise<RelyoInstance> | undefined;

function loadRelyo(): Promise<RelyoInstance> {
  relyo ??= new Promise<RelyoInstance>((resolve, reject) => {
    const script = document.createElement('script');
    script.src = 'https://relyo.de/sdk/relyo.js';
    script.onload = () =>
      window.Relyo
        ? resolve(
            window.Relyo.init({
              relayUrl: 'wss://relyo.de',
              projectKey: 'pk_yourproject',
              connect: 'lazy',
            }),
          )
        : reject(new Error('Relyo not loaded'));
    script.onerror = () => reject(new Error('Relyo not reachable'));
    document.head.append(script);
  }).catch((err: unknown) => {
    relyo = undefined;
    throw err;
  });
  return relyo;
}

export function SupportLink() {
  return (
    <button type="button" onClick={() => loadRelyo().then((r) => r.showCode())}>
      Support
    </button>
  );
}
```

Navigation inside the app (React Router, Next.js links) needs nothing else: the SDK notices new addresses by itself.

## Options

`Relyo.init` takes an object with these fields:

| Option           | Default    | Meaning                                                                                                                                                                                                |
| ---------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `relayUrl`       | –          | Required. Server address, for example `wss://relyo.de`; `https://` becomes `wss://`.                                                                                                                   |
| `projectKey`     | –          | Your website's project key. Without a key, a server with the open service assigns the page to its address (origin).                                                                                    |
| `user`           | –          | Signed-in user, `{ id, name }`. Display only for your supporters, not verified. See [Signed-in users](#signed-in-users).                                                                               |
| `connect`        | `'auto'`   | When the SDK connects, see [Connection](#connection).                                                                                                                                                  |
| `redactSelector` | –          | Additional CSS selector for areas to mask, for example `'.account-details, [data-private]'`. `.relyo-redact` always applies.                                                                           |
| `maskAllInputs`  | `false`    | `true` masks all input fields, not just the sensitive ones.                                                                                                                                            |
| `consentText`    | –          | Your own explanation in the consent dialog; `{agent}` is replaced by the supporter's name.                                                                                                             |
| `cspNonce`       | –          | Nonce for the SDK's style element if your CSP only allows inline styles with a nonce.                                                                                                                  |
| `appVersion`     | –          | Version of your site; shown in the console.                                                                                                                                                            |
| `statusPosition` | `'bottom'` | Start position of the status pill: `'bottom'`, `'bottom-left'`, `'bottom-right'`, `'top'`, `'top-left'` or `'top-right'`. For example `'top-right'` if your page has a fixed navigation at the bottom. |
| `locale`         | `'de'`     | Language of the dialogs. Only German so far.                                                                                                                                                           |

`Relyo.init` returns an instance with these methods:

| Method           | Effect                                                                               |
| ---------------- | ------------------------------------------------------------------------------------ |
| `showCode()`     | requests a code and shows it in a dialog                                             |
| `end()`          | ends a running session or cancels the code or consent                                |
| `identify(user)` | sets the signed-in user, `identify(null)` on logout                                  |
| `destroy()`      | tears down the SDK; a running session can be resumed by a new `init` in the same tab |

## Connection

| `connect` | Behaviour                                                                                                 |
| --------- | --------------------------------------------------------------------------------------------------------- |
| `'auto'`  | with `user`, connect at once (the device appears in the console); anonymous visitors only on `showCode()` |
| `'eager'` | always connect at once, even anonymously                                                                  |
| `'lazy'`  | only on `showCode()`; the connection closes again when the code or session ends                           |

For a support link in the footer, `'lazy'` is the right choice. Before the first connection the SDK stores nothing in the browser, not even an identifier.

## Signed-in users

With `user` (or later `identify`), the devices of signed-in users appear in the console's device list. Supporters with their own account can request a session there without the user reading out a code; consent on the device is still required.

```js
relyo.identify({ id: 'u_123', name: 'Max M.' }); // after login
relyo.identify(null); // on logout
```

The device list is only available for projects with a project key, not with the open service. `id` and `name` are for display only; do not send data your supporters should not see.

## Masking

Masking happens in the user's browser before any data leaves it:

- **Always:** fields with `type="password"`, `type="email"` and `type="tel"`, fields with `autocomplete="cc-…"` (card data) and `autocomplete="one-time-code"`.
- **`.relyo-redact`:** elements with this class and everything inside. The supporter sees an empty area in their place; input inside arrives as `*`.
- **`redactSelector`:** works like `.relyo-redact` for your own selectors.
- **`maskAllInputs: true`:** all input fields.

```html
<div class="relyo-redact">IBAN: DE12 3456 …</div>
```

Mask everything with sensitive data that the rules above do not cover, such as health data or ID numbers in ordinary text fields. The SDK rejects remote input into masked fields.

## Content Security Policy

If your page has a CSP, Relyo needs:

| Directive     | Value                                                       | Purpose                           |
| ------------- | ----------------------------------------------------------- | --------------------------------- |
| `script-src`  | `https://relyo.de`                                          | the script or module              |
| `connect-src` | `wss://relyo.de`                                            | the connection to the server      |
| `style-src`   | `'unsafe-inline'` or `'nonce-…'` with the `cspNonce` option | the SDK dialogs (in a shadow DOM) |

With your own server, use its address instead of `relyo.de`.

## Flow and page changes

- **Consent:** transmission only starts after “Share”. The dialog states what is shared (this tab) and what is not.
- **Status indicator:** during the session a red frame around the window shows that the tab is shared; it covers nothing. A small pill with the supporter's name and “End” sits at the bottom centre by default. Users can drag it to another corner or edge. Neither can be turned off.
- **Reloads and links:** if the page reloads or the user follows a link to another page with Relyo, the SDK resumes the session in the same tab. The server keeps it open for 30 seconds.
- **Control:** clicks, input, scrolling and the keys Enter, Escape, Tab and arrows. Any click or key press of the user's own ends control at once.

## Limitations

- Content from third-party iframes (for example embedded videos or payment forms) appears as an empty area.
- `<video>` and `<canvas>` are not mirrored.
- Remote keys do not trigger browser default actions: Tab does not move focus, arrow keys do not scroll. Enter in a form field submits the form.
- The dialogs are in German only so far.
- Images are sent along so that the supporter can see them even behind a login. Pages with many large images need correspondingly more bandwidth.

## Error messages

If no code can be requested, the dialog shows a message instead of the code (currently in German):

| Message                                                | Cause                                                                                            |
| ------------------------------------------------------ | ------------------------------------------------------------------------------------------------ |
| “Keine Verbindung zum Support-Server …”                | server not reachable, network or CSP blocks it, or too many connections from the same IP address |
| “Gerade sind alle Support-Plätze belegt …”             | limit for concurrent sessions reached                                                            |
| “Support ist für diese Seite derzeit nicht verfügbar.” | site blocked                                                                                     |
| “Der Support-Code konnte nicht angefordert werden.”    | for example an unknown project key or an address that does not match the key                     |
