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
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"}'
{
"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.
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:
<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-ancestorsnaming 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
conversationmode, the embedded page posts a message to the parent window when the agent sends something, so you can close the iframe without polling:
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
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
ssocredential 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.