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

# Setting up embed standard SSO with the v1 signing payload format

> Set up standard SSO for Omni embedding by generating a signed URL that creates an embed user session with a single request.

In this guide, you'll set up standard SSO for Omni embed using the v1 (latest) signing format. As the simplest way to embed Omni, this involves generating and using a single URL to create an embed user session.

<Note>
  **Migrating from v0?** See [Migrating to the v1 embed standard SSO signing format](/embed/setup/standard-sso/migrate-to-latest) to learn about what changed in v1 and how to migrate your code.
</Note>

## Requirements

To follow the steps in this guide, you'll need:

* **Organization Admin** permissions
* To have the **Embed** feature enabled in your Omni instance

If you want to use the [Omni TypeScript SDK](https://www.npmjs.com/package/@omni-co/embed) to generate an embed URL, you'll need `@omni-co/embed` v1.0.0 or later:

```bash theme={null}
npm install @omni-co/embed
```

<Steps titleSize="h2">
  <Step title="Generate an embed secret">
    1. Navigate to **Settings > Embed > Admin** in your Omni instance.
    2. Click the **Add Secret** button below the secrets table.
    3. In the dialog that appears:
       * A 32-character secret key is automatically generated for you
       * Enter a descriptive **Name** to identify the secret's purpose (e.g., "Production", "Staging", or "Partner Integration")

             <Tip>
               Use descriptive names for your secrets to make them easier to identify and manage, especially when rotating secrets or managing multiple environments.
             </Tip>
    4. Click **Add secret** to save the new secret.
  </Step>

  <Step title="Customize session length">
    <Note>
      This step is optional.
    </Note>

    In the [**Embed** settings](/embed/admin/security) of your Omni instance, you can also customize the length of embed sessions using the **Session Length** setting. In this field, enter the number of hours you want sessions to last and click **Change**.
  </Step>

  <Step title="Generate an embed URL">
    <Warning>
      Because embed URLs are signed using your Omni organization's embed secret, it is crucial that your embed URLs are generated server-side rather than client-side. From a security perspective, this protects your embed secret from being exposed to attackers.
    </Warning>

    Omni embed URLs are signed with a secret key used only by your organization. When an Omni server receives the request, it verifies the signature using that secret. If the signature checks out, the request is honored.

    There are three ways to generate a signed embed URL - the SDK (recommended), the API, or manually. **Click the tabs below to view instructions for each approach.**

    <Tabs>
      <Tab title="SDK (Recommended)" icon="trophy" id="sdk-generation">
        Omni's [TypeScript SDK](https://www.npmjs.com/package/@omni-co/embed) is the recommended way to generate embed URLs. Each function returns a complete, signed login URL:

        ```typescript title="Generate a v1 signed embed URL" wrap theme={null}
        import { embedSsoDashboard } from "@omni-co/embed";

        const iframeUrl = await embedSsoDashboard({
          contentId: "123abc",
          externalId: "blob.ross@blobsrus.com",
          name: "Blob Ross",
          organizationName: "blobsrus",
          secret: process.env.OMNI_EMBED_SECRET,
          entity: "Blobs R Us",
          theme: "vibes",
          userAttributes: { planet: "blobine" },
        });
        ```

        <Note>
          The Omni TypeScript SDK utilizes Node's `crypto` module, which is only available in Node environments. Attempting to use Omni TypeScript SDK functions in a client-side context will likely lead to the SDK functions generating improperly signed embed URLs. Always generate embed URLs server-side.
        </Note>

        Every v1 payload carries an expiry. The SDK automatically sets the expiry to 24 hours from the moment the URL is minted. To choose a different lifetime, use the `expiresIn` parameter to define the new lifetime in seconds. The value must be positive and at most seven days (`604800` seconds):

        ```typescript title="Generate a URL that's valid for one hour" wrap highlight={12} theme={null}
        import { embedSsoDashboard } from "@omni-co/embed";

        const iframeUrl = await embedSsoDashboard({
          contentId: "123abc",
          externalId: "blob.ross@blobsrus.com",
          name: "Blob Ross",
          organizationName: "blobsrus",
          secret: process.env.OMNI_EMBED_SECRET,
          entity: "Blobs R Us",
          theme: "vibes",
          userAttributes: { planet: "blobine" },
          expiresIn: 3600,
        });
        ```

        See [URL expiry](/embed/setup/standard-sso/migrate-to-latest#url-expiry) for more information.
      </Tab>

      <Tab title="API" icon="cloud" id="api-generation">
        While the TypeScript SDK is the preferred method for generating signed URLs, you may not be able to leverage it if your backend isn't running a JavaScript runtime. For other languages and environments, Omni offers a stateless API as an escape hatch:

        ```text wrap theme={null}
        https://<YOUR OMNI HOSTNAME>/embed/sso/generate-url
        ```

        The `/embed/sso/generate-url` endpoint only accepts `POST` requests. For `POST` operations, parameters are passed as a JSON object in the request body:

        ```bash wrap title="POST /embed/sso/generate-url" theme={null}
        curl -X POST https://blobsrus.omniapp.co/embed/sso/generate-url \
        -H 'Content-Type: application/json' \
        -d '{
          "contentPath": "/dashboards/12345678",
          "externalId": "abcd1234",
          "name": "Blob Ross",
          "secret": "12345678901234567890123456789012",
          "userAttributes": "%7B%22shop_id%22%3A%22123%22%7D"
        }'
        ```

        This endpoint returns a v1 signed URL for an embedded piece of content. Additionally:

        * The endpoint does not accept a signature parameter, as that is what's being generated.
        * The endpoint requires a `secret` parameter in the request body. The value should be the **Embed secret** you created in step 1.
        * The `nonce` parameter is optional. If not included, one will be automatically generated.
        * JSON-encoded parameter values in the request body (`userAttributes`, `connectionRoles`) should be URL encoded. This applies to the request body only - the endpoint handles payload encoding for the URL it returns. Refer to the [Embed parameters reference](/embed/setup/url-parameters) for a complete list of available parameters.
      </Tab>

      <Tab title="Manual" icon="hand" id="manual-generation">
        <Warning>
          The steps outlined in this section must be followed **exactly** to be successful.
        </Warning>

        Use these steps to generate a v1 signed URL if your backend can't run the TypeScript SDK and you'd rather not call the API:

        <Steps>
          <Step title="Build a JSON object" noAnchor>
            Build a JSON object containing your parameters. The following are required:

            ```markdown theme={null}
            loginUrl      // the full login URL, including scheme and host
            contentPath
            externalId
            name
            nonce
            exp           // expiry, as epoch seconds
            ```

            Any of the [optional parameters](/embed/setup/url-parameters) can be included alongside the above parameters. Omit parameters you aren't setting rather than sending them empty, and note that:

            * **`loginUrl` must exactly match the URL you send the request to.** Omni compares the two after verifying the signature.
            * **`exp` is the absolute expiry in epoch seconds**, following the JWT convention - seconds, not milliseconds. This value must be no more than seven days out. See [URL expiry](/embed/setup/standard-sso/migrate-to-latest#url-expiry) for more information.
            * **JSON-valued parameters are real JSON.** `userAttributes`, `connectionRoles`, `modelRoles`, `customTheme`, and `uiSettings` are objects, and `groups` is an array of strings. Don't stringify them first.
            * **`filterSearchParam` is a URI-encoded query fragment**, because that's what the parameter itself holds.

            For example:

            ```json wrap title="Example parameters" theme={null}
            {
              "loginUrl": "https://blobsrus.embed-omniapp.co/embed/login",
              "contentPath": "/dashboards/123abc",
              "externalId": "luke@example.com",
              "name": "Luke Skywalker",
              "nonce": "hN38NgtnV2B3PMILhKQOpwLyJRP4qVv4",
              "exp": 1787684977,
              "entity": "Acme Corp",
              "theme": "vibes",
              "userAttributes": { "planet": "tatooine" }
            }
            ```
          </Step>

          <Step title="Serialize the object" noAnchor>
            Serialize the object to UTF-8 JSON, compress it with raw DEFLATE ([RFC 1951](https://datatracker.ietf.org/doc/html/rfc1951)), and base64url-encode the result. This encoded string is the `payload` parameter.

            The compression level doesn't matter; neither does the exact byte output of your DEFLATE implementation. Two languages compressing the same parameters will produce different `payload` strings - both are valid. Omni also accepts a zlib-wrapped ([RFC 1950](https://datatracker.ietf.org/doc/html/rfc1950)) payload, which is the default output of several standard libraries.

            The encoded `payload` string must be 64KB or smaller. A fully populated payload is well under 2KB, so if you hit this limit, a parameter - usually `userAttributes` - is carrying more than it should.
          </Step>

          <Step title="Sign the payload string" noAnchor>
            Sign the `payload` string - the base64url text itself, not the compressed bytes - using your secret key with an HMAC-SHA256 digest algorithm, encoded as a base64url string. This is the `signature` parameter.

            Padding is optional, and both the `-_` and `+/` alphabets are accepted, so the default output of most languages' base64url helpers will verify as-is. Refer to [the Base64 spec](https://datatracker.ietf.org/doc/html/rfc4648#page-7) for more information about base64url.
          </Step>

          <Step title="Append payload and signature to the login URL" noAnchor>
            Append `payload` and `signature` to the login URL as URL-encoded query parameters. No other parameters are included in the URL; everything else is carried inside the payload.

            ```python wrap expandable title="Python example" theme={null}
            import base64, hashlib, hmac, json, os, time, urllib.parse, zlib

            secret = os.environ["OMNI_EMBED_SECRET"]
            login_url = "https://blobsrus.embed-omniapp.co/embed/login"
            params = {
                "loginUrl": login_url,
                "contentPath": "/dashboards/123abc",
                "externalId": "luke@example.com",
                "name": "Luke Skywalker",
                "nonce": "hN38NgtnV2B3PMILhKQOpwLyJRP4qVv4",
                # Expire in 24 hours. Epoch seconds, at most 7 days out.
                "exp": int(time.time()) + 24 * 60 * 60,
                "entity": "Acme Corp",
                "theme": "vibes",
                "userAttributes": {"planet": "tatooine"},
            }

            # Raw DEFLATE (RFC 1951): a negative wbits omits the zlib wrapper.
            compressor = zlib.compressobj(9, zlib.DEFLATED, -zlib.MAX_WBITS)
            compressed = compressor.compress(json.dumps(params).encode("utf-8"))
            compressed += compressor.flush()

            payload = base64.urlsafe_b64encode(compressed).decode("ascii")
            signature = base64.urlsafe_b64encode(
                hmac.new(secret.encode("utf-8"), payload.encode("utf-8"), hashlib.sha256).digest()
            ).decode("ascii")

            embed_url = login_url + "?" + urllib.parse.urlencode(
                {"payload": payload, "signature": signature}
            )
            ```
          </Step>
        </Steps>
      </Tab>
    </Tabs>
  </Step>

  <Step title="Review the signed embed URL">
    A v1 login URL has the following form, where all parameters are carried inside the `payload` parameter:

    ```text wrap theme={null}
    https://<your-org-name>.embed-omniapp.co/embed/login?payload=<payload>&signature=<signature>
    ```

    For example, this is a signed embed URL for a `blobsrus` organization:

    ```shell wrap theme={null}
    https://blobsrus.embed-omniapp.co/embed/login?payload=HY7BboMwEER_JZozBSWgQn1q2lNamtJWzd02q4AwtmsvSVCUf6_I9Y3mzVxh3LG3v8FAoGP2UWSZMk7FMMWURkXtgxttL71PtcvuILtXkEA7y2S5kdxBIGtl7JSToY3ZepNLpZGALkzBSrNrIWCmgZ7pIkdvKNVuRAIrR4JAPQ20-hnmszQDhYU7q5eg2-fV_sj2sHnJm49d3b1_ffpzPb99N8Xf4VQsE5Z7niGw1SOtXl3wSMAd3cWnXlFEgilS2DKHXk1MEeIKb6QlhgBLdq63hNty10Osy6p8rIqnsrz9Aw&signature=c14FIQAVH7__vgWG5G3IF8_DF5qPSGx4CPFWqeMmfgc&sdk=%40omni-co%2Fembed%401.0.0-next.3
    ```

    <Note>
      SDK-generated URLs also carry an `sdk` parameter identifying the version that produced the URL. It isn't covered by the signature and isn't required - Omni uses it to understand which SDK versions are in use.
    </Note>
  </Step>

  <Step title="Test the URL with the Embed URL Builder">
    <Note>
      This step is optional.
    </Note>

    While the embed URL builder is primarily intended for internal embedding, you can use it to test the format of your [URL parameters](/embed/setup/url-parameters).

    1. First, you'll need your content's unique ID:

           <AccordionGroup>
             <Accordion title="Locate dashboard IDs">
               You can find the dashboard ID by:

               * **Opening the document settings**. Navigate to **File > Document settings** in the dashboard and then click **Settings**. The **Identifier** field contains the dashboard ID.
               * **Using the dashboard's URL**. The string after `/dashboards` is the dashboard's ID; for example:
                 ```markdown wrap theme={null}
                 https://blobsrus.omniapp.co/dashboards/12db1a0a
                 ```
             </Accordion>

             <Accordion title="Locate workbook IDs">
               * **If the workbook is attached to a dashboard**, its content ID is the same as the dashboard
               * **If the workbook doesn't have a dashboard**, you can find the ID by navigating to **File > Document settings**, then clicking **Settings**. The **Identifier** field contains the document ID.

                 **Note**: Embedding a workbook creates a copy of the workbook for that embed user so their changes are not reflected back into the application's production version of the workbook.
             </Accordion>
           </AccordionGroup>

    2. Navigate to **Admin > Embed > URL Builder tab**.

    3. Fill in the required fields, noted below:
       * **Content Path**
         * **For dashboards**: `/dashboards/<content_id>`
         * **For workbooks**: `/w/<content_id>`
         * **For apps**: `/apps/<content_id>`
       * **External ID** - Any alphanumeric value
       * **Name** - Any alphanumeric value

    4. Generate your URL and embed!
  </Step>
</Steps>
