> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Signed-token public share plane: sharing with recipients who have no account

> How Orbit's public share surfaces — conversation transcripts, recordings, callback status pages, analytics dashboard embeds, and queue-appointment links — carry their own authentication inside an HMAC-signed token with optional password and email-domain gates, why a recipient cannot cross tenants by editing the URL, and what to tell your security team when they ask whether that link is safe.

# Signed-token public share plane

Some of what Orbit captures is only useful if a person **outside your Orbit account** can see it: a customer disputing what was said, an auditor reading one conversation thread, a customer checking where their callback is in the queue, a reseller's client embedding an analytics dashboard, an end customer booking a queue appointment from a hosted page. Each of those reaches the recipient through a **public share link** — a URL that works in a fresh browser with no sign-in, no API key, and no Clerk session.

The authentication in every one of those URLs is a signed **actor token**: a compact credential minted by your account that is itself the whole proof. This page explains the one model that drives all the share families, what protects the link in flight and at view time, and the limits of that protection — it is the page to hand your security team when they ask "is it safe to share this link?" It complements the object-specific pages ([Recording lifecycle](/concepts/recording-lifecycle), [Session replay](/concepts/session-replay)) that each cover their own slice.

## The actor-token model — the token is the authentication

Every share family has an authenticated mint endpoint behind your session. When an agent or integration calls it, Orbit builds a small JSON payload holding exactly the identifiers the resolver needs, signs it with HMAC-SHA256, and returns a URL the recipient can open:

* **Conversation transcripts** — `POST /api/v1/conversations/:id/share` mints the link a resolver serves at `GET /api/v1/public/conversations/:token/transcript`.
* **Recordings** — `POST /api/v1/recordings/:id/share` mints the link served at `GET /api/v1/public/recordings/:token`.
* **Callback status** — the callback-dispatch scheduler texts the customer a link to `GET /api/v1/public/callbacks/:token` (status, one-tap cancel, one-tap reschedule).
* **Analytics dashboard embeds** — `POST /api/v1/analytics/dashboards/share` mints the URL consumed by `GET /api/v1/public/analytics/dashboards/embed?token=…`; embedding your own dashboard view in an iframe carries the token as a query parameter instead.
* **Queue appointments** — a tenant-keyed hosted booking page (`GET /api/v1/public/queues/:tenantId/:queueId/availability`) that publishes a queue's bookable slots and lets an end customer self-book a callback slot.

The wire format is the same everywhere: `base64url(JSON payload) + "." + base64url(HMAC-SHA256(secret, body))`. What that implies for you: the link is a capability — possession is sufficient to open it — until you add one of the gates below.

## Tenant identity travels inside the signed body, never the URL

The one rule the whole plane rests on: the tenant and object identity are **inside the signed payload**, not in the URL path parameters. A transcript token signs `{ conversationId, tenantSchema, organizationId, issuedAt, expiresAt, … }`, and the resolver derives which tenant to read from the verified payload — never from the URL. Editing a path segment produces a token whose signature no longer matches, and verification fails before any storage is touched (see [Tenant binding](#tenant-binding-from-the-signed-payload-never-the-url) below). A recipient cannot hop to another tenant's data by rewriting the link; the bytes they would need are bound into the HMAC tag they cannot recompute.

## Token verification, and the deliberate vagueness of a 410

Verification runs in a fixed order before any content is read:

1. Decode the payload and recompute the HMAC-SHA256 tag.
2. Compare tags in constant time.
3. Validate the payload shape and wire version.
4. Reject on expiry.

**Every** failure — malformed, tampered, expired, future-dated, wrong wire version — returns the same `410 Gone` with the same non-disclosing message. This is deliberate: distinguishing "this token was tampered with" from "this token expired" tells a probing attacker which kind of manipulation succeeded, so the resolver refuses to leak it.

## Optional access gates: password and email-domain

On top of the bearer token, the mint can bind two optional gate fields into the same signed body:

* **Password** — the mint stores a keyed digest of the password, never the plaintext. The resolver recomputes the digest from the recipient-supplied password and compares in constant time. The `conversationId` is folded into the digest, so a stolen digest cannot be replayed against a different conversation.
* **Email-domain allowlist** — up to 25 normalized domains. The recipient types their email and the domain must match before any content renders. This is a soft deterrent against casually forwarded links, layered on top of the token; it is not email verification and is never a replacement for the token itself.

When a gate is present, the resolver evaluates it from the verified payload before any transcript or media bytes leave the server. An unsatisfied gate returns `401` with an unlock form that re-submits to the same URL — the protected content is never sent to a recipient who only holds the link.

## The JWKS story: ecosystem-wide verification

Share tokens minted by the unified actor-token service respond to a broader verification rule: the platform publishes the **public** half of its signing key as a standard JWK Set at `GET /api/v1/public/.well-known/actor-token-jwks.json`. An external verifier — a merchant, an auditor, a customer's security team — reads the token's key id, fetches the set, and verifies the signature **offline**, without being handed a shared secret. Token headers advertise the set's own URL, so a token and the document that publishes its verifier can never drift apart.

The JWKS document only ever carries public keys; publishing it is safe by design, and rotation becomes "re-publish one public key" rather than an emergency distribution of secrets.

## Tenant binding from the signed payload, never the URL

The resolver's tenant reference is derived from the signed `tenantSchema` inside the verified payload. Because every byte of the payload is bound into the HMAC tag, there is no path where a recipient supplies the tenant identity — not a query parameter, not a header, not the URL path. The database read is confined to the tenant the token was minted against, and cross-tenant reads are structurally impossible, not merely checked.

## One rate-limit envelope for the public surface

Every public share resolver applies the platform's IP-keyed public unauthenticated envelope, the same cap as the other public signed-token landing surfaces. Brute-forcing a link is bounded; a mint behind your session is not throttled by this envelope, so issuance stays cheap at API volume.

## Read-only invariant

The public share surfaces are strictly read-only resolvers: a pure SELECT of the bound thread, recording, callback row, dashboard layout, or availability window, rendered as HTML or JSON. Callback status and queue appointments are the exception — they deliberately accept the recipient's one-tap cancel/reschedule/book action through the same tenant-bound token, and the same flow guards mutations to the entity the token travels in. Nothing outbound is emitted from any resolver.

## The worked example: mint a transcript link and gate it

1. From a dashboard session, mint the link:

   ```http theme={null}
   POST /api/v1/conversations/cnv_9u2x/share
   Authorization: Bearer <your session>
   {
     "ttl": 604800000,
     "password": "north-star"
   }
   ```

   Orbit clamps the TTL to the share family's allowed band (for transcript links, 1 day to 90 days, defaulting to 7 days), hashes the password, signs the payload, and returns the full public URL.

2. Hand the URL plus the password to the recipient through two separate channels.

3. The recipient opens the link. Verification passes; the password gate is unsatisfied, so the resolver renders the unlock form (`401` in the resolver's internal flow, an HTML form to the human). The correct password renders the transcript; a wrong one re-renders the same unlock form until the IP-keyed rate envelope throttles the attempts.

4. After the TTL expires, or after the tenant rotates the signing secret, the same URL returns the same 410 as a forged token — no longer a live document.

The same shape holds across the plane: a recording link plays only while the recording exists and the token verifies, a dashboard embed serves only the layout snapshot the mint froze, and a callback link acts only on the callback row the token binds.

## Which share families participate

| Family                              | Mint endpoint                             | Public resolver                                                                                                           | Optional gates          |   |
| ----------------------------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | ----------------------- | - |
| Conversation transcripts            | `POST /api/v1/conversations/:id/share`    | `GET /api/v1/public/conversations/:token/transcript`                                                                      | password, email domains |   |
| Recordings                          | `POST /api/v1/recordings/:id/share`       | `GET /api/v1/public/recordings/:token`                                                                                    | —                       |   |
| Callback status (cancel/reschedule) | minted by the dispatch scheduler text     | `GET /api/v1/public/callbacks/:token`, \`POST /api/v1/public/callbacks/:token/cancel                                      | reschedule\`            | — |
| Analytics dashboard embeds          | `POST /api/v1/analytics/dashboards/share` | `GET /api/v1/public/analytics/dashboards/embed?token=…`                                                                   | —                       |   |
| Queue appointments (self-booking)   | tenant-hosted page opt-in                 | `GET /api/v1/public/queues/:tenantId/:queueId/availability`, `POST /api/v1/public/queues/:tenantId/:queueId/appointments` | —                       |   |

Survey instruments mint their own response tokens on a separate instrument pipeline — see [The survey lifecycle](/concepts/survey-lifecycle-model); the model is the same one-plane family with its own payload shape.

## What to tell your security team

* The link is a capability; possession is sufficient. Share it only with the intended recipient, and gate it with a password or email-domain allowlist when the content is sensitive.
* A tampered or expired link is indistinguishable from a wrong link — `410 Gone` in both cases, with no oracle to probe.
* Tenant identity is signed into the token, so forwarding the URL does not broaden access to other tenants' data.
* Verification is decidable offline: the public JWK Set at `GET /api/v1/public/.well-known/actor-token-jwks.json` proves the signature without access to any shared secret.
* Rate limits bound link-guessing, and the read-only invariant means a resolver never emits outbound traffic on the recipient's behalf.
