<!-- Generated from the canonical Teloring documentation source. Do not edit this file. -->

Canonical page: https://docs.teloring.com/api/guide-sso
Last modified: 2026-08-20T20:49:57.000Z

# SSO login

Your users already work in your product. SSO login lets them reach Teloring from
inside it, without a second password: your backend asks for a link, your user
opens it, and they land in the console signed in as a specific agent.

Two shapes, for two different jobs:

**Full system** — the whole console, exactly as if they had signed in. Use it for
a "Open in Teloring" button.

**Specific conversation** — one contact's chat and nothing else. No navigation,
no conversation list, no dashboard. Use it when your user is looking at a
customer in *your* system and wants to message them: open it in an iframe, send
the message, close the iframe.

## Before it will work

The `sso` scope alone grants nothing. In **Settings → API**, on the credential,
you must also set:

**Which agents it may sign in as.** A multi-select. An agent not on the list
answers `403 agent_not_allowed`, and the error lists who is allowed. An empty
list means the credential can impersonate nobody — the safe default when
somebody ticks the scope and saves without choosing.

**Which origins may embed it,** if you plan to use an iframe. As many as you
need, one per line, scheme and host only:

```
https://app.example.com
https://portal.example.co.il
https://crm.example.com:8443
```

Both are editable later without rotating the secret.

## Full system

```bash
curl -X POST https://api.teloring.com/v1/sso/login \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"agent_id": "agent_12", "mode": "full"}'
```

```json
{
  "object": "sso_login",
  "url": "https://console.teloring.com/sso/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…",
  "mode": "full",
  "agent_id": "agent_12",
  "agent_name": "Dana Levi",
  "embed": false,
  "expires_at": "2026-08-20T10:34:31+00:00",
  "expires_in": 600,
  "single_use": true
}
```

Redirect the browser to `url`. They land on the dashboard, signed in as Dana.

## Specific conversation

Identify the contact by `phone` or `email`. Add `inbox_id` when you know which
channel to use.

```bash
curl -X POST https://api.teloring.com/v1/sso/login \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "agent_12",
    "mode": "conversation",
    "contact": { "phone": "+972501234567" },
    "inbox_id": "3",
    "embed": true
  }'
```

What your user sees depends on what exists, and the API tells you in advance
under `target`:

| Situation | What opens |
| --- | --- |
| A conversation with that contact is already open | That conversation, ready to reply |
| No conversation, and you passed `inbox_id` | The composer, contact **and** inbox already chosen — only the message is left (or the template, on WhatsApp outside the 24-hour window) |
| No conversation, and no `inbox_id` | The composer with the contact chosen; your user picks the inbox |

The contact must already exist. An unknown phone or email answers
`404 contact_not_found` — create the contact, or start the conversation with
`POST /v1/conversations`, which will create it for you.

## Embedding in your own product

Set `embed: true` and open the URL in an iframe:

```html
<iframe
  src="https://console.teloring.com/sso/eyJhbGciOi…"
  style="width: 100%; height: 640px; border: 0;"
  allow="microphone">
</iframe>
```

Three things happen because you asked for an embed:

- The pages are served with `Content-Security-Policy: frame-ancestors` naming
  **only** the origins you configured. Any other site framing the same URL gets
  nothing.
- The session cookie is issued `SameSite=None; Secure; Partitioned`. Partitioned
  matters: the cookie lives in *your* site's cookie partition, so it cannot be
  replayed from anywhere else — which is also what keeps cross-site request
  forgery closed on an embedded session.
- In `conversation` mode, the embedded page posts a message to the parent window
  when the agent sends something, so you can close the iframe without polling:

```js
window.addEventListener('message', (event) => {
  if (event.origin !== 'https://console.teloring.com') return;
  if (event.data?.source === 'teloring' && event.data.type === 'message_sent') {
    closeTeloringPanel();          // your code
  }
});
```

Requesting `embed: true` with no origins configured answers `400
no_embed_origins` rather than quietly handing you a page no browser will frame.

## How the link is protected

A URL that bypasses the login screen is a credential, so it is treated like one.

**Ten minutes, one use.** The link expires after ten minutes and is redeemable
exactly once. A second attempt — a copied URL, a duplicated tab, a prefetching
proxy — answers `409`. Mint one per use; never cache them.

**Re-checked at redemption.** The link carries no permissions of its own. When it
is opened, the credential is re-read (still active? still holds `sso`? still
allows this agent?) and the agent is re-read (still exists? still active?). So
removing an agent from the allowlist, or revoking the credential, kills every
link already issued — within the ten-minute window, not after it.

**Fails closed.** If the one-time-use store is unreachable, redemption is
refused rather than allowed. A login-bypass link that silently becomes
replayable is worse than one that briefly stops working.

**Audited.** Every link minted and every redemption is written to the audit log
with the credential, the agent and the mode, so `agent.sso_login` in the audit
trail always names who let it happen.

## Handling a link that will not open

If the link is expired, already used, or its credential or agent no longer
qualifies, the user sees a small Teloring error page — **not** a redirect to the
sign-in form. That is deliberate: inside your iframe, a Teloring login form is
both useless and confusing to somebody who has no Teloring account of their own.

That page says one of four things, and they split into two groups:

| What the user sees | HTTP | Cause | Does a new link fix it? |
| --- | --- | --- | --- |
| You have already opened this link | `409` | The link was redeemed once already — a refresh, a back button, or a second click | Yes |
| This sign-in link has expired | `401` | More than 10 minutes passed between minting and opening | Yes |
| This sign-in link is no longer authorised | `403` | The credential was revoked, lost the `sso` scope, or the agent was removed from its allow-list | No |
| This sign-in link no longer works | `403` | The agent it signs in as was deactivated | No |

The first two are the everyday ones, and the page tells the user to go back and
open it again — so they resolve themselves as soon as your product mints a new
link. The last two are configuration changes on the Teloring side, so the page
sends the user to whoever administers the account instead of round a retry loop
that cannot succeed.

Your integration's job is simply to mint a fresh link. Because they are cheap
and single-use, the right pattern is to request one at the moment the user
clicks, never to store one. A refresh of your own page should mint a new link
rather than reload the iframe's existing URL — that URL is spent, and reloading
it is the single most common way to land on the "already opened" page.

## A complete example

```python
def open_teloring_chat(agent_id, customer_phone, inbox_id=None):
    """Return an iframe URL for messaging this customer. Call it per click."""
    payload = {
        "agent_id": agent_id,
        "mode": "conversation",
        "contact": {"phone": customer_phone},
        "embed": True,
    }
    if inbox_id:
        payload["inbox_id"] = inbox_id

    response = requests.post(
        "https://api.teloring.com/v1/sso/login",
        headers=client._auth_header(),
        json=payload,
        timeout=10,
    )

    if response.status_code == 404:
        # No such contact yet — create the conversation, which creates the
        # contact, then try again.
        return None
    response.raise_for_status()
    return response.json()["url"]
```

## Security notes for your side

- **Mint links server-side only.** Your `sso` credential must never reach the
  browser: anyone holding it can sign in as any allow-listed agent.
- **Authorise on your side first.** Teloring checks that the *credential* may
  impersonate the agent. It cannot check that *this particular user of yours*
  should be allowed to — that is your call, and you must make it before minting.
- **Map your users to agents deliberately.** A shared agent id means your audit
  log says "the API" where it should say a person.
- **Keep the origin list tight.** Every origin you add is a site that may frame a
  signed-in Teloring session.
