54321G
Liftmira
HomeArticleAboutContactDocs

Getting started

Exchanging a session token

What happens on every page load: your server gets a 60-second code with the License key, the browser exchanges it for a session token, and the viewer loads the encrypted model. Requests, answers and error codes.

Also available in Vietnamese

This guide covers the embed code path, where you write one route on your own server. On WordPress and Shopify the plugin and the app make these calls for you (see Three ways to integrate).

Your License key stays on your server. A page load needs two requests that come from two different places. Your server asks Comnoira for a code that lives 60 seconds and works once. The visitor's browser then trades that code for the session token the viewer uses. Both requests are needed because Comnoira checks which website the browser is on, and only the browser request can show that.

One page load, step by step
One page load, step by stepSequence diagram with five participants. The page gets a code through your server, exchanges it in the browser, opens the viewer, loads the encrypted model, and renews the session before it ends.Browser and SDKYour serverComnoira APIViewer (iframe)Model storage1Call your route(getSessionToken)2POST /viewer/sessionX-License-Key3Exchange code,expiresIn 604Code only,never the License key5POST /session/exchangeOrigin headerChecks: code unused and under 60 s,Origin allowed, license active6Session token7Opens iframe withthe session tokenThe viewer loads the model8POST /viewer/bootstrap9GET /viewer/assets/{id}/url10Signed link andsession key id11GET /viewer/key/{id}12Session key,once, 60 s13Encrypted file(signed link)Decrypts the file in the browserRenewing the session14POST /session/refreshabout 60 s before expiry15New session tokenIf refresh fails: getSessionToken again,then a new exchange
Text version of this diagram
  1. Browser and SDK → Your server: Call your route (getSessionToken)
  2. Your server → Comnoira API: POST /viewer/session X-License-Key
  3. Comnoira API → Your server: Exchange code, expiresIn 60
  4. Your server → Browser and SDK: Code only, never the License key
  5. Browser and SDK → Comnoira API: POST /session/exchange Origin header
  6. Comnoira API → Browser and SDK: Session token
  7. Browser and SDK → Viewer (iframe): Opens iframe with the session token
  8. Viewer (iframe) → Comnoira API: POST /viewer/bootstrap
  9. Viewer (iframe) → Comnoira API: GET /viewer/assets/{id}/url
  10. Comnoira API → Viewer (iframe): Signed link and session key id
  11. Viewer (iframe) → Comnoira API: GET /viewer/key/{id}
  12. Comnoira API → Viewer (iframe): Session key, once, 60 s
  13. Viewer (iframe) → Model storage: Encrypted file (signed link)
  14. Browser and SDK → Comnoira API: POST /session/refresh about 60 s before expiry
  15. Comnoira API → Browser and SDK: New session token

Step 1: your server asks for an exchange code

Your server calls the Comnoira API with the License key in the X-License-Key header. The body carries the publicKey and the Showcase ID of the Showcase to show.

curl -X POST https://api.comnoira.com/api/v1/viewer/session \
  -H "X-License-Key: $COMNOIRA_LICENSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"publicKey": "pk_live_xxx", "showcaseId": "SHOWCASE_ID"}'

The body also accepts an optional viewerUid. The answer is not a usable session token. It is a code that works once:

{
  "sessionToken": "6f1c2a9e-0000-0000-0000-000000000000",
  "expiresIn": 60,
  "viewerUid": null
}

The field is called sessionToken but holds the exchange code, and expiresIn is 60 seconds. Ask for a new code on every page load. Do not cache a code and do not reuse one.

The Backend refuses the request, before it mints a code, in these cases. Each answer has the form {"success": false, "error": {"code": "...", "message": "..."}}.

  • LICENSE_KEY_MISSING (HTTP 403): the X-License-Key header is absent.
  • LICENSE_KEY_INVALID (400): the License key is not valid.
  • LICENSE_INACTIVE (400): the license is not active or has passed its end date.
  • LICENSE_KEY_SHOP_MISMATCH (403): the License key belongs to another shop than the Showcase.
  • SHOWCASE_NOT_FOUND (404): no active Showcase has this Showcase ID.
  • HTTP 400 without a specific code: the publicKey does not belong to that Showcase ID.
  • SHOWCASE_NOT_ACTIVATED (400): your shop is not active yet. Comnoira activates it. Until then use a Preview.
  • SHOWCASE_NOT_IN_PRODUCTION (409): the Showcase is a demo or a Preview. A License key only mints sessions for production Showcases.
  • RATE_LIMITED (429): too many requests from one server address. The limit in the Backend is 2000 requests in 15 minutes.

Step 2: your page starts the SDK

Your route returns the code to the browser, and the SDK takes it from your getSessionToken function. The call is Comnoira.init(publicKey, showcaseId, options). The old name ThreeHub is the same object and still works.

<div id="comnoira-viewer" style="height: 600px"></div>
<script src="https://cdn.comnoira.com/sdk/v1/3dhub.min.js"></script>
<script>
  Comnoira.init("pk_live_xxx", "SHOWCASE_ID", {
    container: "comnoira-viewer",
    getSessionToken: function () {
      return fetch("/your-backend-route")
        .then(function (res) { return res.json(); })
        .then(function (data) { return data.sessionToken; });
    },
  });
</script>

The Dashboard generates this snippet for your Showcase in HTML, React, Vue and Angular, with matching server routes in Node.js, Python, PHP and Java. Copy it from there, since it also fills in your publicKey and Showcase ID.

options holds exactly one of three things, and init throws when none or more than one is given.

  • getSessionToken: a function that returns the code, either as a string or as an object { sessionToken }. Use it for single-page apps and for any page where the code is fetched in the browser. The SDK calls it again whenever it needs a new code.
  • sessionToken: a code your server has already placed in the page. Use it for pages rendered on the server. The code lives 60 seconds, so the page must be rendered for that visitor and opened at once. A cached or reloaded copy of the page carries a spent code and fails with EXCHANGE_TOKEN_INVALID. With only sessionToken, the SDK cannot ask for a new code, and when the session ends it emits VIEWER_SESSION_EXPIRED so your page decides what to do.
  • demo: true: see Getting started. It works only on origins Comnoira lists.

container is the id of an element, written without #. The SDK does not accept the element itself. If the id does not exist, init fails with Container #id not found. If you leave container out, the SDK adds a div at the end of the page body, 100% wide and 500px high by default.

init returns a promise that rejects when the session cannot be set up. The SDK also emits VIEWER_ERROR with { code, message, fatal }, where code is one of the codes named in this guide. A failure of your own route, or no network, shows up as SERVICE_UNAVAILABLE.

Step 3: the SDK exchanges the code

The SDK sends the code from the visitor's browser:

POST https://api.comnoira.com/api/v1/viewer/session/exchange
{ "exchangeToken": "6f1c2a9e-..." }

The browser sets the Origin header to the page's own origin, and a script on the page cannot change it. The Backend then checks, in this order: the request rate, that the code is unused and not expired, that the Showcase is active, that the origin is allowed, and that the license is active. If all pass, it answers with the session token:

{
  "sessionToken": "eyJ...",
  "expiresIn": 3600,
  "viewerUid": "..."
}

expiresIn is the session length of your license plan, or the time left on your license if that is shorter. The value above is an example, not a fixed value. The SDK keeps viewerUid in the browser's local storage under 3dhub_uid, so a returning visitor keeps their settings.

Errors at this step:

  • EXCHANGE_TOKEN_INVALID (HTTP 403): the code is unknown, already used or older than 60 seconds. The Backend does not tell these apart.
  • ORIGIN_NOT_ALLOWED (403): the page's domain is not on the Showcase's list of allowed domains, or the request had no Origin header.
  • SHOWCASE_NOT_FOUND (404), LICENSE_INACTIVE (400): the Showcase or the license changed between the two steps.
  • RATE_LIMITED (429): one IP address may exchange 30 codes in 15 minutes. Visitors behind one office address share that limit.

The origin check compares the host name only. The scheme and the port do not count. An entry such as shop.example.com matches that host. An entry such as *.example.com matches example.com and every subdomain. The Dashboard also offers an Open mode that skips the check, meant for testing only.

The SDK does not retry a failed exchange on 429 or 503, because the code is spent. It emits VIEWER_SERVER_BUSY with willRetry: false so your page can say so.

Step 4: the viewer loads the model

The SDK opens viewer.comnoira.com in an iframe and passes it the session token. The viewer then asks the Backend for the Showcase and its content. For each model file it works as follows.

  1. It asks for the file's address. The Backend answers with a signed link to the encrypted file and the id of a session key.
  2. It asks for the session key by that id. The Backend gives each session key out once, and the key stays valid for 60 seconds.
  3. It downloads the encrypted file and decrypts it in the browser with that session key.

The files are stored encrypted, so the link alone does not show the model.

Keeping the session alive

About 60 seconds before the session token expires, the SDK sends it to POST /api/v1/viewer/session/refresh. This call needs no License key. It gets the same origin check as the exchange, and the old token stops working when the new one is issued. If the refresh fails and you gave getSessionToken, the SDK calls it for a new code and runs the exchange again. If the refresh fails and you did not, the SDK makes three attempts in total, spaced 5 seconds and then 10 seconds apart, and then emits VIEWER_SESSION_EXPIRED.

When the Backend answers 429 or 503 with a Retry-After header on a refresh, the SDK waits that long and tries again, up to twice, and emits VIEWER_SERVER_BUSY each time.

Why the browser makes the second request

The Origin header on a request from your server is whatever the sender writes, so it shows nothing about the page. The browser fills in the header itself, so the exchange is the one request where Comnoira can see which website is asking. The code is an opaque value, not a token the API accepts as a login, so a leaked code cannot open the viewer after its 60 seconds or a second use.

If the viewer stays empty or shows an error, go to Troubleshooting.