Identity Providers - Cognigy Documentation

​ Prerequisites

​ Limitations

​ Create an IdP App

To configure an IdP app, follow these steps:

  1. Log in to the Auth0 Dashboard and select your tenant.
  2. In the left-side menu, go to Applications > Applications and click + Create Application.
  3. Enter a name, for example, Cognigy.AI, select Regular Web Applications as the app type, and click Create.
  4. Go to the Settings tab and copy the values from the fields in the Basic Information section. You will use them later to configure the IdP in Cognigy.AI: - Domain — used in the idpIssuer parameter in the request payload.
    • Client ID — used in the idpClientId parameter in the request payload.
    • Client Secret — used in the idpClientSecret parameter in the request payload.
  5. On the Settings tab, configure the following using the API base URL and organization ID from the Prerequisites section: - Application Login URI — enter https://<api-base-url>/auth/oidc/callback/<organization-id>.
    • Allowed Callback URLs — enter https://<api-base-url>/auth/oidc/login/callback/<organization-id>.
    • Allowed Logout URLs — enter https://<api-base-url>/logout/<organization-id>.
    • Allowed Web Origins — enter https://*.cognigy.ai.
    • Allowed Origins (CORS) — enter https://*.cognigy.ai.

1

Set Up an App

  1. Log in to the Auth0 Dashboard and select your tenant.
  2. In the left-side menu, go to Applications > Applications and click + Create Application.
  3. Enter a name, for example, Cognigy.AI, select Single Page Web Applications as the app type, and click Create.
  4. Go to the Settings tab, configure the following using the API base URL and organization ID from the Prerequisites section: - Application Login URI — enter https://<api-base-url>/auth/saml/login/<organization-id>.
    • Allowed Callback URLs — enter https://<api-base-url>/auth/oidc/login/callback/<organization-id>.
  5. On the Addons tab, activate the SAML2 Web App add-on. Auth0 opens the add-on settings dialog. From the Usage tab, copy and save the values from the following fields for later use in the API request payload to configure the IdP in Cognigy.AI: - Issuer — used in the idpIssuer parameter in the request payload.
    • Identity Provider Login URL — used in the idpLoginEndpoint parameter in the request payload.
    • Identity Provider Certificate — used in the idpCertificate parameter in the request payload. Click Download Auth0 certificate. Base64-encode the certificate without line breaks. You can use the following terminal command:

PowerShell

Bash (macOS)

Bash (Linux)

     [Convert]::ToBase64String([IO.File]::ReadAllBytes(".\path-to-certificate"))
     ```
 base64 -i ./<path-to-certificate> | tr -d '\n'
 ```
     cat ./<path-to-certificate> | base64 -w0
     ```

2

Map Roles

For each user which you want to apply SSO to, set `user_metadata` to map the user’s name and Cognigy.AI role. To do so, follow these steps:

1. Go to **User Management > Users**, select the user, scroll to the **Metadata** section, and paste the following JSON into the **user\_metadata** field:

{ "family_name": "", "given_name": "", "role": "" }


The `role` value must match a [role in Cognigy.AI](https://docs.cognigy.com/ai/administer/access/admin-center/access-control), for example, `admin` or `base_role`.
2. Go to **Actions > Library**, click **Create Action**, and configure the following:

- **Name** — enter a name.
- **Login / Post Login** — activate this option.
- In the editor, paste the following code:

exports.onExecutePostLogin = async (event, api) => { const userMetadata = event.user.user_metadata || {};

api.samlResponse.setAttribute( "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", event.user.email ); api.samlResponse.setAttribute("firstName", userMetadata.given_name || ""); api.samlResponse.setAttribute("lastName", userMetadata.family_name || ""); api.samlResponse.setAttribute("role", userMetadata.role || ""); };


3. Deploy the Action.

1

Set Up an App

1. Log in to the [Azure portal](https://portal.azure.com/) with an administrator account and navigate to **Microsoft Entra ID**.
2. In the top bar, click **\+ Add** and select **Enterprise applications**. The **Browse Microsoft Entra Gallery** page opens.
3. In the top bar, click **\+ Create your own application**.
4. Enter a name, for example, `Cognigy.AI`, select **Integrate any other app you don’t find in the gallery (Non-gallery)**, and click **Create**.
5. On the new app page, open **Single sign-on** in the left navigation and select **SAML** as the sign-on method.
6. In the **Basic SAML Configuration** section, click **Edit** and configure the following:   - **Identifier (Entity ID)** — enter `https://<api-base-url>/auth/saml/login/<organization-id>`.
   - **Reply URL (Assertion Consumer Service URL)** — enter `https://<api-base-url>/auth/saml/login/<organization-id>`.
   - **Sign on URL** — enter `https://<api-base-url>/auth/saml/login/<organization-id>`.
   - _(Optional)_ **Logout Url** — enter `https://<cognigy-url>/slo/<organization-id>`.
7. Click **Save**.

2

Map User Attributes

1. In the **User Attributes and Claims** section, click **Edit** and confirm that the following claims are mapped to the user’s profile attributes:

| Claim name | Value |
| --- | --- |
| `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress` | `user.mail` |
| `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname` | `user.givenname` |
| `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname` | `user.surname` |

Cognigy.AI uses the email claim to identify the user and to create the Cognigy.AI account on the first login.

3

Get Authentication Data

1. In the **SAML Signing Certificate** section, locate **Certificate (Base64)** and click **Download**. Save the file locally. Use this certificate as `idpCertificate` in the API request to [configure the IdP in Cognigy.AI](https://docs.cognigy.com/ai/administer/installation/identity-providers#configure-sso-in-cognigy-ai). You need to encode the certificate as base64 without newlines beforehand. To do so, use the following terminal command:

PowerShell

Bash (macOS)

Bash (Linux)

base64 -i ./ | tr -d '\n'


cat ./ | base64 -w0


2. In the **Set up `<application name>`** section, copy the following values to be used in the API request to [configure the IdP in Cognigy.AI](https://docs.cognigy.com/ai/administer/installation/identity-providers#configure-sso-in-cognigy-ai):

- **Login URL** — used in the `idpLoginEndpoint` parameter in the request payload.
- _(Optional)_ **Logout URL** — used in the `idpLogoutUrl` parameter in the request payload when you enable single logout.

4

Assign Roles

1. In the top bar, search for `App registrations` and select this option.
2. In the **All applications** tab, search for the app you created and select it.
3. In the left-side menu, go to **Manage > Manifest** and deactivate the default `User` and `msiam_access` roles. To do so:   1. Set `isEnabled` to `false` for the `User` and `msiam_access` roles, for example:
     "appRoles": [\
       {\
         "allowedMemberTypes": [\
           "User"\
         ],\
         "description": "User",\
         "displayName": "User",\
         "id": "18d14569-c3bd-439b-9a66-3a2aee01d14f",\
         "isEnabled": false,\
         "origin": "Application",\
         "value": null\
       },\
       {\
         "allowedMemberTypes": [\
           "User"\
         ],\
         "description": "msiam_access",\
         "displayName": "msiam_access",\
         "id": "b9632174-c057-4f7e-951b-be3adc52bfe6",\
         "isEnabled": false,\
         "origin": "Application",\
         "value": null\
       }\
     ]
     ```
  1. In the left-side menu, go to Manage > App roles, select the User role, and click Delete on the right-side pane. Do the same for the msiam_access role.
  2. Paste the following JSON code in the appRoles array:

Roles Array

{
     "allowedMemberTypes": [\
       "User"\
     ],
     "displayName": "Admin",
     "id": "8d17fe88-c0ca-4903-ae2a-a51098998bc1",
     "isEnabled": true,
     "description": "The Admin role in Cognigy.AI",
     "value": "admin"
},
{
     "allowedMemberTypes": [\
       "User"\
     ],
     "displayName": "API Keys",
     "id": "8d17fe88-c0ca-4903-ae2a-a51098998bc2",
     "isEnabled": true,
     "description": "The apiKeys role in Cognigy.AI",
     "value": "apiKeys"
},
{
     "allowedMemberTypes": [\
       "User"\
     ],
     "displayName": "Base",
     "id": "8d17fe88-c0ca-4903-ae2a-a51098998bc3",
     "isEnabled": true,
     "description": "The base role in Cognigy.AI",
     "value": "base_role"
},
{
     "allowedMemberTypes": [\
       "User"\
     ],
     "displayName": "Full Support User",
     "id": "8d17fe88-c0ca-4903-ae2a-a51098998bc4",
     "isEnabled": true,
     "description": "Admin privileges, no user assignments in Cognigy.AI",
     "value": "fullSupportUser"
},
{
     "allowedMemberTypes": [\
       "User"\
     ],
     "displayName": "OData",
     "id": "8d17fe88-c0ca-4903-ae2a-a51098998bc5",
     "isEnabled": true,
     "description": "The OData role in Cognigy.AI",
     "value": "odata"
},
{
     "allowedMemberTypes": [\
       "User"\
     ],
     "displayName": "Project Manager",
     "id": "8d17fe88-c0ca-4903-ae2a-a51098998bc6",
     "isEnabled": true,
     "description": "The Project Manager role in Cognigy.AI",
     "value": "projectManager"
},
{
     "allowedMemberTypes": [\
       "User"\
     ],
     "displayName": "User Manager",
     "id": "8d17fe88-c0ca-4903-ae2a-a51098998bc7",
     "isEnabled": true,
     "description": "The User Manager role in Cognigy.AI",
     "value": "userManager"
},
{
     "allowedMemberTypes": [\
       "User"\
     ],
     "displayName": "Administrator",
     "id": "8d17fe88-c0ca-4903-ae2a-a51098998bc8",
     "isEnabled": true,
     "description": "The Administrator role in Live Agent",
     "value": "liveAgentAdmin"
},
{
     "allowedMemberTypes": [\
       "User"\
     ],
     "displayName": "Agent",
     "id": "8d17fe88-c0ca-4903-ae2a-a51098998bc9",
     "isEnabled": true,
     "description": "The Agent role in Live Agent",
     "value": "liveAgentAgent"
},
{
     "allowedMemberTypes": [\
       "User"\
     ],
     "displayName": "Supervisor",
     "id": "8d17fe88-c0ca-4903-ae2a-a51098998bd1",
     "isEnabled": true,
     "description": "The Supervisor role in Live Agent",
     "value": "liveagentSupervisor"
},
{
     "allowedMemberTypes": [\
       "User"\
     ],
     "displayName": "View user details",
     "id": "8d17fe88-c0ca-4903-ae2a-a51098998bd2",
     "isEnabled": true,
     "description": "The role to view user details in Cognigy.AI",
     "value": "userDetailsViewer"
},
{
     "allowedMemberTypes": [\
       "User"\
     ],
     "displayName": "Voice Gateway User",
     "id": "8d17fe88-c0ca-4903-ae2a-a51098998bd3",
     "isEnabled": true,
     "description": "The Account scope in Voice Gateway",
     "value": "voiceGatewayUser"
},
{
     "allowedMemberTypes": [\
       "User"\
     ],
     "displayName": "Basic Support User",
     "id": "8d17fe88-c0ca-4903-ae2a-a51098998bd4",
     "isEnabled": true,
     "description": "Partial Admin, read-only, no assignments, no OData/API, no Knowledge AI in Cognigy.AI",
     "value": "basicSupportUser"
},
{
     "allowedMemberTypes": [\
       "User"\
     ],
     "displayName": "Project Assigner",
     "id": "8d17fe88-c0ca-4903-ae2a-a51098998bd5",
     "isEnabled": true,
     "description": "Assigns Agents, read-only access, no global roles, limited features in Cognigy.AI",
     "value": "projectAssigner"
}
  1. In the left-side menu, go to Users and groups and assign the app to the users or groups that must have access to Cognigy.AI through SSO.

1

Add a Custom Attribute

You need to add a custom attribute that Cognigy.AI uses when the user logs in for the first time. To do so, follow these steps:

  1. Log in to the Google Admin console.
  2. In the left-side menu, go to Directory > Users.
  3. At the top of the Users list, click More options > Manage user attributes.
  4. Click the Add Custom Attribute button and configure the following:

| Name | Info type | Visibility | Number of values | | --- | --- | --- | --- | | First Name | Text | Visible to user and admin | Single value | | Last Name | Text | Visible to user and admin | Single value | | Role | Text | Visible to user and admin | Single value |

If a user doesn’t have a role assigned, Cognigy.AI assigns base_role to the user.

  1. Click Add.

2

Set Up an App

  1. In the left-side menu, go to Apps > SAML Apps.
  2. Click the plus button in the lower-right corner and select Setup My Own Custom App.
  3. In the Google IdP Information dialog, copy the SSO URL value and download the certificate. You’ll use the SSO URL as idpLoginEndpoint and the certificate as idpCertificate in the API request to configure the IdP in Cognigy.AI. You need to encode the certificate as base64 without newlines beforehand. To do so, use the following terminal command:

PowerShell

Bash (macOS)

Bash (Linux)

[Convert]::ToBase64String([IO.File]::ReadAllBytes(".\path-to-certificate"))
base64 -i ./<path-to-certificate> | tr -d '\n'
cat ./<path-to-certificate> | base64 -w0

Click Next. 4. Enter a name for the app, for example, Cognigy.AI, and click Next. 5. On the service provider details page, configure the following:

Click Next. 6. On the Attribute Mapping page, add the following attribute mappings so that Cognigy.AI receives the user’s first name, last name, and role from Google:

Service Provider Attribute Custom Attribute Field
firstName Cognigy.AI SSO First Name
lastName Cognigy.AI SSO Last Name
role Cognigy.AI SSO Role
  1. Click Finish, then activate the app for the users or organizational units that must have access to Cognigy.AI through Google SSO.

1

Set Up an App

  1. Log in to your Okta Admin Console with an administrator account.
  2. In the left-side menu, go to Applications > Applications and click Create New App.
  3. Select SAML 2.0 as the sign-on method and click Create.
  4. On the General Settings tab, enter an app name, for example, Cognigy.AI SSO, optionally upload a logo, and click Next.
  5. On the Configure SAML tab, configure the following: - Single sign-on URL — enter https://<api-base-url>/auth/saml/login/<organization-id>.
    • Use this for Recipient URL and Destination URL — keep selected.
    • Audience URI (SP Entity ID) — enter https://<api-base-url>/auth/saml/login/<organization-id>.
    • Name ID format — select Unspecified.
    • Application username — select Email.
    • Update application username on — select Create and update.
  6. (Optional) To encrypt SAML requests, click Show Advanced Settings, set Assertion Encryption to Encrypted, and upload an X.509 certificate. You will need the private key of this certificate to register the IdP in Cognigy.AI.

2

Map User Attributes

  1. In the Attribute Statements section, add the following attributes so that Cognigy.AI receives the user profile data it needs to create the account:
Name Name format Value
email Unspecified user.email
firstName Unspecified user.firstName
lastName Unspecified user.lastName

Click Next. 2. On the Feedback tab, select I’m an Okta customer adding an internal app and click Finish.

3

Get Authentication Data

  1. On the Applications page, select the app you created, then open the Sign On tab.
  2. In the SAML Signing Certificates section, click View Setup Instructions or Identity Provider metadata and copy the following values: - Identity Provider Single Sign-On URL — used in the idpLoginEndpoint parameter in the request payload.
    • Identity Provider Issuer — used in the idpIssuer parameter in the request payload.
    • X.509 Certificate — used in the idpCertificate parameter in the request payload. You need to encode the certificate as base64 without newlines beforehand. If you enabled SAML request encryption, you also need to encode the private key as base64. You can use the following terminal command:

PowerShell

Bash (macOS)

Bash (Linux)

     [Convert]::ToBase64String([IO.File]::ReadAllBytes(".\path-to-certificate-or-private-key"))
     ```
 base64 -i ./<path-to-certificate-or-private-key> | tr -d '\n'
 ```
     cat ./<path-to-certificate-or-private-key> | base64 -w0
     ```

4

Assign Users

1. In the left-side menu of the Admin Console, go to **Directory > Profile Editor**, and click **Profile** next to the profile of the app you created.
2. Click **Add Attribute** and configure the following:   - **Data type** — select **string**.
   - **Display name** — enter `Role`.
   - **Variable name** — enter `role`.
   - _(Optional)_ **Description** — enter a relevant description, for example, `The role of the user in Cognigy.AI`.
   - **Enum** — activate **Define enumerated list of values**.
   - **Attribute member** — enter a display name and the value of the role to which SSO applies, for example, `Base Role` and `base_role`. The value must match a [role in Cognigy.AI](https://docs.cognigy.com/ai/administer/access/admin-center/access-control).
   - **Attribute required** — select this option.

Click **Save**.
3. Go to **Applications > Applications** and select the app you created.
4. On the **Assignments** tab, click the Edit button next to the user’s name and assign the role they should have in Cognigy.AI. This user can log in via SSO.

1

Set Up an App

1. Log in to your OneLogin Administration portal with an administrator account.
2. In the top bar, click **Applications**, then click **Add App** in the upper-right corner.
3. Search for `SAML Custom Connector (Advanced)` in the search field and select this option.
4. In the **Display Name** field, enter a name that identifies the integration, for example, `Cognigy.AI SSO`. Optionally upload icons. Click **Save**.
5. On the new app page, click the **Configuration** tab in the left-side menu and configure the following:

- **ACS (Consumer) URL** — enter `https://<api-base-url>/auth/saml/login/<organization-id>`.
   - **ACS (Consumer) URL Validator** — enter `https://<api-base-url>/auth/saml/login/<organization-id>`.
   - _(Optional)_ **Single Logout URL** — enter `https://<cognigy-url>/slo/<organization-id>`.

Click **Save**.
6. In the left-side menu, go to the **Parameters** tab, configure the following parameter and value pairs, and select the **Include in SAML assertion** option for each pair:

| Parameter | Value |
| --- | --- |
| `email` | Email |
| `firstName` | First Name |
| `lastName` | Last Name |
| `role` | User Role |

Click **Save** to apply the attribute mapping.

2

Get Authentication Data

1. In the left-side menu, go to the **SSO** tab and copy the following values:   - **X.509 Certificate** — click **View Details** and download the certificate. You will use its contents as `idpCertificate`. You need to encode the certificate as base64 without newlines beforehand. You can use the following terminal command:

PowerShell

Bash (macOS)

Bash (Linux)
    [Convert]::ToBase64String([IO.File]::ReadAllBytes(".\path-to-certificate"))
    ```
        base64 -i ./<path-to-certificate> | tr -d '\n'
        ```
    cat ./<path-to-certificate> | base64 -w0
    ```
  1. In the left-side menu, navigate to Users > Roles and click New Role.
  2. Add the following Cognigy.AI roles one by one, select the app you created, and click Save: - admin
    • apiKeys
    • base_role
    • livechat
    • odata
    • projectManager
    • userManager
  3. In the left-side menu, go to the Users tab and select a user for SSO. On the user page, enter the role they have in Cognigy.AI in the role field. The role must match the roles you configured in step 3.
  4. Click Save. The user can log in via SSO.

​ Configure SSO in Cognigy.AI

After the SSO app is ready, use the POST /v2.0/identityprovider/configure method to register the SSO configuration in Cognigy.AI.

Send the API request with the following parameters:

API Request Example

-X POST \

JavaScript

Python

  -H "Content-Type: application/json" \
  -H "X-API-Key: <your-api-token>" \
  -d '{
    "idpType": "oidc",
    "idpIssuer": "<DOMAIN>",
    "idpClientId": "<CLIENT ID>",
    "idpClientSecret": "<CLIENT SECRET>",
    "idpIdTokenSignedResponseAlg": "RS256",
    "idpTokenEndpointAuthMethod": "client_secret_basic"
  }'
const response = await fetch("https://<api-base-url>/v2.0/identityprovider/configure", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-API-Key": "<your-api-token>"
  },
  body: JSON.stringify({
    idpType: "oidc",
    idpIssuer: "<DOMAIN>",              // Domain from the Auth0 app Settings tab
    idpClientId: "<CLIENT ID>",         // Client ID from the Auth0 app Settings tab
    idpClientSecret: "<CLIENT SECRET>", // Client Secret from the Auth0 app Settings tab
    idpIdTokenSignedResponseAlg: "RS256",
    idpTokenEndpointAuthMethod: "client_secret_basic"
  })
});
import requests

response = requests.post(
    "https://<api-base-url>/v2.0/identityprovider/configure",
    headers={
        "Content-Type": "application/json",
        "X-API-Key": "<your-api-token>"
    },
    json={
        "idpType": "oidc",
        "idpIssuer": "<DOMAIN>",                # Domain from the Auth0 app Settings tab
        "idpClientId": "<CLIENT ID>",           # Client ID from the Auth0 app Settings tab
        "idpClientSecret": "<CLIENT SECRET>",   # Client Secret from the Auth0 app Settings tab
        "idpIdTokenSignedResponseAlg": "RS256",
        "idpTokenEndpointAuthMethod": "client_secret_basic"
    }
)

Send the API request with the following data:

API Request Example

-X POST \

JavaScript

Python

  -H "Content-Type: application/json" \
  -H "X-API-Key: <your-api-token>" \
  -d '{
    "idpType": "saml",
    "idpIssuer": "urn:<your-tenant>.auth0.com",
    "idpLoginEndpoint": "https://<your-tenant>.auth0.com/samlp/<client-id>",
    "idpCertificate": "-----BEGIN CERTIFICATE-----\n<certificate-body>\n-----END CERTIFICATE-----",
    "idpIdentifierFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
  }'
const response = await fetch("https://<api-base-url>/v2.0/identityprovider/configure", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-API-Key": "<your-api-token>"
  },
  body: JSON.stringify({
    idpType: "saml",
    idpLoginEndpoint: "https://<your-tenant>.auth0.com/samlp/<client-id>",                          // Identity Provider Login URL from the Auth0 add-on Usage tab
    idpIssuer: "urn:<your-tenant>.auth0.com",                                                    // Identity Provider Issuer from the Auth0 add-on Usage tab
    idpCertificate: "-----BEGIN CERTIFICATE-----\n<certificate-body>\n-----END CERTIFICATE-----",      // Identity Provider Certificate from the Auth0 add-on Usage tab
    idpIdentifierFormat: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
  })
});
import requests

response = requests.post(
    "https://<api-base-url>/v2.0/identityprovider/configure",
    headers={
        "Content-Type": "application/json",
        "X-API-Key": "<your-api-token>"
    },
    json={
        "idpType": "saml",
        "idpLoginEndpoint": "https://<your-tenant>.auth0.com/samlp/<client-id>",                          # Identity Provider Login URL from the Auth0 add-on Usage tab
        "idpIssuer": "urn:<your-tenant>.auth0.com",                                                    # Identity Provider Issuer from the Auth0 add-on Usage tab
        "idpCertificate": "-----BEGIN CERTIFICATE-----\n<certificate-body>\n-----END CERTIFICATE-----",      # Identity Provider Certificate from the Auth0 add-on Usage tab
        "idpIdentifierFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
    }
)

Send the API request with the following data:

API Request Example

cURL

JavaScript

Python

curl -X POST "https://<api-base-url>/v2.0/identityprovider/configure" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: <your-api-token>" \
  -d '{
    "idpType": "saml",
    "idpIssuer": "https://<api-base-url>/auth/saml/login/<organization-id>",
    "idpLoginEndpoint": "https://login.microsoftonline.com/4a7853bd-xxxx-xxxx-xxxx-xxxxxxxxxxxx/saml2",
    "idpCertificate": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t...",
    "idpLogoutUrl": "https://login.microsoftonline.com/4a7853bd-xxxx-xxxx-xxxx-xxxxxxxxxxxx/saml2"
  }'
const response = await fetch("https://<api-base-url>/v2.0/identityprovider/configure", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-API-Key": "<your-api-token>"
  },
  body: JSON.stringify({
    idpType: "saml",
    idpIssuer: "https://<api-base-url>/auth/saml/login/<organization-id>",                                // Cognigy.AI SSO URL
    idpLoginEndpoint: "https://login.microsoftonline.com/4a7853bd-xxxx-xxxx-xxxx-xxxxxxxxxxxx/saml2",    // Login URL from the Set up `<application name>` section
    idpCertificate: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t...",                                            // Base64-encoded contents of the downloaded .crt file
    idpLogoutUrl: "https://login.microsoftonline.com/4a7853bd-xxxx-xxxx-xxxx-xxxxxxxxxxxx/saml2"          // Logout URL from the Set up `<application name>` section (optional, for SP-initiated SLO)
  })
});
import requests

response = requests.post(
    "https://<api-base-url>/v2.0/identityprovider/configure",
    headers={
        "Content-Type": "application/json",
        "X-API-Key": "<your-api-token>"
    },
    json={
        "idpType": "saml",
        "idpIssuer": "https://<api-base-url>/auth/saml/login/<organization-id>",                                # Cognigy.AI SSO URL
        "idpLoginEndpoint": "https://login.microsoftonline.com/4a7853bd-xxxx-xxxx-xxxx-xxxxxxxxxxxx/saml2",    # Login URL from the Set up `<application name>` section
        "idpCertificate": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t...",                                            # Base64-encoded contents of the downloaded .crt file
        "idpLogoutUrl": "https://login.microsoftonline.com/4a7853bd-xxxx-xxxx-xxxx-xxxxxxxxxxxx/saml2"          # Logout URL from the Set up `<application name>` section (optional, for SP-initiated SLO)
    }
)

Don’t include the PEM header, the PEM footer, or any newline characters in the certificate you submit to Cognigy.AI. A malformed certificate string causes the SSO configuration request to fail.

Send the API request with the following data:

API Request Example

cURL

JavaScript

Python

curl -X POST "https://<api-base-url>/v2.0/identityprovider/configure" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: <your-api-token>" \
  -d '{
    "idpType": "saml",
    "idpIssuer": "https://<api-base-url>/auth/saml/login/<organization-id>",
    "idpLoginEndpoint": "https://accounts.google.com/o/saml2/idp?idpid=XXXX",
    "idpCertificate": "MIIDdTCCAl2gAwIBAgIJAKx..."
  }'
const response = await fetch("https://<api-base-url>/v2.0/identityprovider/configure", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-API-Key": "<your-api-token>"
  },
  body: JSON.stringify({
    idpType: "saml",
    idpIssuer: "https://<api-base-url>/auth/saml/login/<organization-id>",   // Cognigy.AI SSO URL
    idpLoginEndpoint: "https://accounts.google.com/o/saml2/idp?idpid=XXXX",  // SSO URL from Google IdP Information dialog in the Google Admin console
    idpCertificate: "MIIDdTCCAl2gAwIBAgIJAKx..."                              // Certificate from Google IdP Information dialog, base64-encoded on a single line
  })
});
import requests

response = requests.post(
    "https://<api-base-url>/v2.0/identityprovider/configure",
    headers={
        "Content-Type": "application/json",
        "X-API-Key": "<your-api-token>"
    },
    json={
        "idpType": "saml",
        "idpIssuer": "https://<api-base-url>/auth/saml/login/<organization-id>",    # Cognigy.AI SSO URL
        "idpLoginEndpoint": "https://accounts.google.com/o/saml2/idp?idpid=XXXX",   # SSO URL from Google IdP Information dialog in the Google Admin console
        "idpCertificate": "MIIDdTCCAl2gAwIBAgIJAKx..."                               # Certificate from Google IdP Information dialog, base64-encoded on a single line
    }
)

Send the API request with the following data:

API Request Example (Without Encryption)

cURL

JavaScript

Python

curl -X POST "https://<api-base-url>/v2.0/identityprovider/configure" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: <your-api-token>" \
  -d '{
    "idpType": "saml",
    "idpIssuer": "<OKTA_IDENTITY_PROVIDER_ISSUER>",
    "idpLoginEndpoint": "<OKTA_SINGLE_SIGN_ON_URL>"
  }'
const response = await fetch("https://<api-base-url>/v2.0/identityprovider/configure", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-API-Key": "<your-api-token>"
  },
  body: JSON.stringify({
    idpType: "saml",
    idpIssuer: "<OKTA_IDENTITY_PROVIDER_ISSUER>",     // Identity Provider Issuer from the Okta Sign On tab
    idpLoginEndpoint: "<OKTA_SINGLE_SIGN_ON_URL>"        // Identity Provider Single Sign-On URL from the Okta Sign On tab
  })
});
import requests

response = requests.post(
    "https://<api-base-url>/v2.0/identityprovider/configure",
    headers={
        "Content-Type": "application/json",
        "X-API-Key": "<your-api-token>"
    },
    json={
        "idpType": "saml",
        "idpIssuer": "<OKTA_IDENTITY_PROVIDER_ISSUER>",     # Identity Provider Issuer from the Okta Sign On tab
        "idpLoginEndpoint": "<OKTA_SINGLE_SIGN_ON_URL>"        # Identity Provider Single Sign-On URL from the Okta Sign On tab
    }
)

API Request Example (With Encryption)

cURL

JavaScript

Python

curl -X POST "https://<api-base-url>/v2.0/identityprovider/configure" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: <your-api-token>" \
  -d '{
    "idpType": "saml",
    "idpIssuer": "<OKTA_IDENTITY_PROVIDER_ISSUER>",
    "idpLoginEndpoint": "<OKTA_SINGLE_SIGN_ON_URL>",
    "idpCertificate": "<OKTA_X509_CERTIFICATE>",
    "decryptionPrivateKey": "<OKTA_PRIVATE_KEY>"
  }'
const response = await fetch("https://<api-base-url>/v2.0/identityprovider/configure", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-API-Key": "<your-api-token>"
  },
  body: JSON.stringify({
    idpType: "saml",
    idpIssuer: "<OKTA_IDENTITY_PROVIDER_ISSUER>",     // Identity Provider Issuer from the Okta Sign On tab
    idpLoginEndpoint: "<OKTA_SINGLE_SIGN_ON_URL>",    // Identity Provider Single Sign-On URL from the Okta Sign On tab
    idpCertificate: "<OKTA_X509_CERTIFICATE>",        // X.509 Certificate from the Okta Sign On tab, base64-encoded on a single line
    decryptionPrivateKey: "<OKTA_PRIVATE_KEY>"        // Private key matching the certificate uploaded to Okta, base64-encoded on a single line
  })
});
import requests

response = requests.post(
    "https://<api-base-url>/v2.0/identityprovider/configure",
    headers={
        "Content-Type": "application/json",
        "X-API-Key": "<your-api-token>"
    },
    json={
        "idpType": "saml",
        "idpIssuer": "<OKTA_IDENTITY_PROVIDER_ISSUER>",     # Identity Provider Issuer from the Okta Sign On tab
        "idpLoginEndpoint": "<OKTA_SINGLE_SIGN_ON_URL>",       # Identity Provider Single Sign-On URL from the Okta Sign On tab
        "idpCertificate": "<OKTA_X509_CERTIFICATE>",        # X.509 Certificate from the Okta Sign On tab, base64-encoded on a single line
        "decryptionPrivateKey": "<OKTA_PRIVATE_KEY>"        # Private key matching the certificate uploaded to Okta, base64-encoded on a single line
    }
)

Send the API request with the following data:

API Request Example

cURL

JavaScript

Python

curl -X POST "https://<api-base-url>/v2.0/identityprovider/configure" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: <your-api-token>" \
  -d '{
    "idpType": "saml",
    "idpIssuer": "https://app.onelogin.com/saml/metadata/<id>",
    "idpLoginEndpoint": "https://<subdomain>.onelogin.com/trust/saml2/http-post/sso/<id>",
    "idpCertificate": "<ONELOGIN_X509_CERTIFICATE>",
    "idpLogoutEndpoint": "https://<subdomain>.onelogin.com/trust/saml2/http-redirect/slo/<id>"
  }'
const response = await fetch("https://<api-base-url>/v2.0/identityprovider/configure", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-API-Key": "<your-api-token>"
  },
  body: JSON.stringify({
    idpType: "saml",
    idpIssuer: "https://app.onelogin.com/saml/metadata/<id>",                                      // Issuer URL from the OneLogin SSO tab
    idpLoginEndpoint: "https://<subdomain>.onelogin.com/trust/saml2/http-post/sso/<id>",           // SAML 2.0 Endpoint (HTTP) from the OneLogin SSO tab
    idpCertificate: "<ONELOGIN_X509_CERTIFICATE>",                                                 // X.509 Certificate from the OneLogin SSO tab, base64-encoded on a single line
    idpLogoutEndpoint: "https://<subdomain>.onelogin.com/trust/saml2/http-redirect/slo/<id>"       // SLO Endpoint from the OneLogin SSO tab (optional)
  })
});
import requests

response = requests.post(
    "https://<api-base-url>/v2.0/identityprovider/configure",
    headers={
        "Content-Type": "application/json",
        "X-API-Key": "<your-api-token>"
    },
    json={
        "idpType": "saml",
        "idpIssuer": "https://app.onelogin.com/saml/metadata/<id>",                                      # Issuer URL from the OneLogin SSO tab
        "idpLoginEndpoint": "https://<subdomain>.onelogin.com/trust/saml2/http-post/sso/<id>",           # SAML 2.0 Endpoint (HTTP) from the OneLogin SSO tab
        "idpCertificate": "<ONELOGIN_X509_CERTIFICATE>",                                                 # X.509 Certificate from the OneLogin SSO tab, base64-encoded on a single line
        "idpLogoutEndpoint": "https://<subdomain>.onelogin.com/trust/saml2/http-redirect/slo/<id>"       # SLO Endpoint from the OneLogin SSO tab (optional)
    }
)

A successful request returns a confirmation that the identity provider configuration has been saved. The Cognigy.AI login page now displays a Log in with SSO button for users in your organization.

​ Test the SSO Login

  1. On the Cognigy.AI login page, enter the email address of a user assigned to the IdP app and click Log in with SSO. Cognigy.AI redirects you to the IdP and prompts you for your IdP credentials. After authentication, the IdP redirects back to Cognigy.AI and logs you in.
  2. Click the user icon in the lower-left corner and select Logout.
  3. Enter your email address again and click Log in with SSO. This time, you are logged in directly without re-entering your IdP credentials.

​ More Information

Last modified onJune 9, 2026

Previous\ \ Single Sign-on using SAML 2.0

Ctrl+I

ChatVoice

Click the call button to start talking.

Cognigy AssistantReady to talk

Connecting…

Powered by Cognigy.AI

Cookie Consent

We use cookies to recognize your repeated visits and preferences, as well as to measure the effectiveness of our documentation and whether users find what they're searching for.

With your consent, you're helping us make our documentation better. To find out more about the cookies we use, see our Privacy Policy.

Accept

'; cachedClickBlockingCode = css + js; return cachedClickBlockingCode; }

// Helper: Inject click-blocking code into iframe function injectClickBlocking(iframeDoc) { if (!iframeDoc || !iframeDoc.head) return false;

try { const style = iframeDoc.createElement('style'); style.textContent = 'a,button,input,select,textarea,[onclick],[role="button"],input[type="button"],input[type="submit"],[href],[tabindex]:not([tabindex="-1"]){pointer-events:none!important;cursor:default!important}a:hover,button:hover{cursor:default!important;opacity:0.7!important}'; iframeDoc.head.appendChild(style);

const script = iframeDoc.createElement('script'); script.textContent = '(function(){var preventClick=function(e){e.preventDefault();e.stopPropagation();e.stopImmediatePropagation();return false;};document.addEventListener("click",preventClick,true);document.addEventListener("mousedown",preventClick,true);document.addEventListener("mouseup",preventClick,true);document.addEventListener("touchstart",preventClick,true);document.addEventListener("touchend",preventClick,true);document.addEventListener("contextmenu",preventClick,true);window.addEventListener("beforeunload",function(e){e.preventDefault();e.returnValue="";return "";},true);try{Object.defineProperty(window,"location",{value:{href:"#",replace:function(){},assign:function(){}},writable:false,configurable:false});}catch(e){}var originalPushState=history.pushState;history.pushState=function(){return false;};var originalReplaceState=history.replaceState;history.replaceState=function(){return false;};var originalOpen=window.open;window.open=function(){return null;};var allElements=document.querySelectorAll("*");for(var i=0;i 0 || replacedViewers.length > 0) { const currentUrl = window.location.href; if (!processedUrls.has(currentUrl)) { processedUrls.add(currentUrl); lastUrl = currentUrl; } return; }

const currentUrl = window.location.href; if (currentUrl === lastUrl && lastUrl !== null && processedUrls.has(currentUrl)) { return; }

// Optimized: Use cached viewer query const viewers = getUninitializedViewers();

if (viewers.length === 0) { if (periodicCheckInterval) { clearInterval(periodicCheckInterval); periodicCheckInterval = null; } return; }

// Final safety check if (viewers.length > 0) { if (currentUrl === lastUrl && lastUrl !== null && processedUrls.has(currentUrl)) { viewers.forEach(function(viewer) { viewer.setAttribute('data-initialized', 'true'); }); return; }

if (currentUrl === lastUrl && lastUrl !== null) { if (existingContainers.length > 0) { processedUrls.add(currentUrl); viewers.forEach(function(viewer) { viewer.setAttribute('data-initialized', 'true'); }); return; } } }

isInitializing = true; let processedAny = false;

// Optimized: Process viewers in batch viewers.forEach(function(viewer) { if (processedElements.has(viewer) || viewer.hasAttribute('data-initialized')) { return; }

if (viewer.closest('.single-page-viewer-container')) { return; }

if (!viewer.parentNode) { return; }

const isInContentArea = contentArea === document.body || contentArea.contains(viewer); if (!isInContentArea) { return; }

const src = viewer.getAttribute('data-src'); const hint = viewer.getAttribute('data-hint') || ''; const defaultZoom = parseFloat(viewer.getAttribute('data-zoom')) || DEFAULT_ZOOM; const dataHeight = viewer.getAttribute('data-height'); const height = dataHeight ? parseFloat(dataHeight) : null;

if (!src) { // Don't set isInitializing = false here - it affects all viewers in the loop return; }

processedElements.add(viewer); viewer.setAttribute('data-initialized', 'true');

let zoom = defaultZoom;

// Optimized: Use document fragment for DOM construction const fragment = document.createDocumentFragment(); const container = document.createElement('div'); container.className = 'single-page-viewer-container';

const wrapper = document.createElement('div'); wrapper.className = 'single-page-viewer-wrapper';

const controlsBar = document.createElement('div'); controlsBar.className = 'single-page-viewer-controls';

if (hint) { const hintDiv = document.createElement('div'); hintDiv.className = 'single-page-viewer-hint'; hintDiv.innerHTML = '' + hint + ''; controlsBar.appendChild(hintDiv); }

const iframeWrapper = document.createElement('div'); iframeWrapper.className = 'single-page-viewer-iframe-wrapper'; iframeWrapper._scrollHandlerAdded = false;

// Optimized: Get height without temporary DOM manipulation const baseHeightFromCSS = height || NORMAL_HEIGHT;

const scaleDiv = document.createElement('div'); scaleDiv.className = 'single-page-viewer-scale'; const initialHeight = baseHeightFromCSS; scaleDiv.style.cssText = 'transform: scale(' + zoom + '); transform-origin: top left; width: ' + (100 / zoom) + '%; height: ' + initialHeight + 'px; transition: transform 0.2s ease;';

const iframe = document.createElement('iframe'); let iframeSrc = src; if (iframeSrc && !iframeSrc.startsWith('http') && !iframeSrc.startsWith('//')) { if (!iframeSrc.startsWith('/')) { iframeSrc = '/' + iframeSrc; } }

iframe.title = 'Single Page Viewer'; iframe.className = 'single-page-viewer-iframe'; iframe.style.width = '100%'; iframe.style.height = initialHeight + 'px'; iframe.loading = 'lazy'; iframe.setAttribute('scrolling', 'no'); iframe.setAttribute('sandbox', 'allow-same-origin allow-scripts allow-forms allow-popups allow-modals');

const clickBlocker = document.createElement('div'); clickBlocker.className = 'single-page-viewer-click-blocker';

if (iframeSrc.endsWith('.html') && !iframeSrc.startsWith('http')) { fetch(iframeSrc) .then(function(response) { return response.text().then(function(htmlContent) { if (htmlContent.includes('Page Not Found') || (htmlContent.includes('__next') && htmlContent.includes('mintlify') && !htmlContent.includes('Page saved with SingleFile'))) { throw new Error('Received 404 page instead of HTML file.'); } const blockingCode = getClickBlockingCode(); if (htmlContent.includes('')) { htmlContent = htmlContent.replace('', blockingCode + ''); } else if (htmlContent.includes('')) { htmlContent = htmlContent.replace('', '' + blockingCode); } else { if (htmlContent.includes('')) { htmlContent = htmlContent.replace('', '' + blockingCode + ''); } else { htmlContent = blockingCode + htmlContent; } } iframe.srcdoc = htmlContent; }); }); } else { iframe.src = iframeSrc; }

iframe.addEventListener('load', function() { setTimeout(function() { updateIframeDimensions(iframeWrapper, scaleDiv, iframe, zoom); }, 100);

try { const iframeDoc = iframe.contentDocument || iframe.contentWindow.document; injectClickBlocking(iframeDoc); } catch (e) { // Cross-origin iframe - cannot block clicks } }, { once: true });

iframe.addEventListener('error', function() { // Iframe failed to load - error handling is silent }, { once: true });

scaleDiv.appendChild(iframe); iframeWrapper.appendChild(scaleDiv); if (!iframeSrc.endsWith('.html') || iframeSrc.startsWith('http')) { iframeWrapper.appendChild(clickBlocker); }

if (hint) { wrapper.appendChild(controlsBar); } wrapper.appendChild(iframeWrapper); container.appendChild(wrapper); fragment.appendChild(container);

try { if (viewer.parentNode) { if (viewer === contentArea || viewer.id === 'content-area') { isInitializing = false; return; }

const viewerId = 'viewer-' + Date.now() + '-' + Math.random().toString(36).slice(2, 11); viewer.id = viewerId; viewer.setAttribute('data-viewer-id', viewerId); container.setAttribute('data-viewer-id', viewerId);

// Optimized: Single DOM insertion using fragment if (viewer.nextSibling) { viewer.parentNode.insertBefore(fragment, viewer.nextSibling); } else { viewer.parentNode.appendChild(fragment); }

// Batch style updates Object.assign(viewer.style, { display: 'none', visibility: 'hidden', position: 'absolute', width: '1px', height: '1px', overflow: 'hidden', opacity: '0', pointerEvents: 'none', margin: '0', padding: '0' });

viewer.setAttribute('data-initialized', 'true'); viewer.setAttribute('data-viewer-replaced', 'true');

processedAny = true; invalidateQueryCache();

requestAnimationFrame(function() { updateIframeDimensions(iframeWrapper, scaleDiv, iframe, zoom); }); } } catch (e) { isInitializing = false; return; } });

if (processedAny) { const currentUrl = window.location.href; processedUrls.add(currentUrl); lastUrl = currentUrl; invalidateQueryCache(); }

isInitializing = false; }

// Wait for content area to be available function waitForContentArea(callback, maxAttempts, attempt) { attempt = attempt || 0; maxAttempts = maxAttempts || 20;

const contentArea = document.querySelector('#content-area') || document.querySelector('.mdx-content');

if (contentArea && document.contains(contentArea)) { cachedContentArea = contentArea; callback(); } else if (attempt < maxAttempts) { setTimeout(function() { waitForContentArea(callback, maxAttempts, attempt + 1); }, 500); } else { cachedContentArea = document.body; callback(); } }

// Optimized: Initialize with better state management function initialize() { const currentUrl = window.location.href; if (lastUrl !== currentUrl && lastUrl !== null) { lastUrl = currentUrl; processedUrls.clear(); invalidateQueryCache(); }

if (typeof window !== 'undefined') { window.__lastFullUrl = window.location.href; }

if (lastThemeClass === null && document.documentElement) { lastThemeClass = document.documentElement.className; }

if (!themeObserver && document.documentElement) { themeObserver = new MutationObserver(function(mutations) { for (let i = 0; i < mutations.length; i++) { const mutation = mutations[i]; if (mutation.type === 'attributes' && mutation.attributeName === 'class') { const currentThemeClass = document.documentElement.className; if (lastThemeClass !== null && lastThemeClass !== currentThemeClass) { themeToggleInProgress = true;

// Optimized: Lock containers synchronously const contentArea = getContentArea(); if (contentArea) { const containers = getContainers(true); containers.forEach(function(container) { if (container.getAttribute('data-position-locked') !== 'true') { const rect = container.getBoundingClientRect(); Object.assign(container.style, { position: 'fixed', top: rect.top + 'px', left: rect.left + 'px', width: rect.width + 'px', zIndex: '9999', margin: '0' }); container.setAttribute('data-position-locked', 'true'); containerPositions.set(container, { top: rect.top, left: rect.left, width: rect.width, height: rect.height }); } }); }

let repositionAttempts = 0; const maxRepositionAttempts = 30;

const repositionInterval = setInterval(function() { repositionAttempts++; repositionContainersAfterThemeToggleCore(); // Use core function for immediate repositioning if (repositionAttempts >= maxRepositionAttempts) { clearInterval(repositionInterval); } }, 100);

setTimeout(function() { clearInterval(repositionInterval); themeToggleInProgress = false; repositionContainersAfterThemeToggleCore(); // Use core function for final repositioning unlockContainers(); }, 3000); } lastThemeClass = currentThemeClass; } } });

themeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] }); }

if (typeof MutationObserver !== 'undefined') { setupObserver(); }

waitForContentArea(function() { setTimeout(function() { initSinglePageViewers(); setTimeout(function() { const remaining = getUninitializedViewers(true); if (remaining.length > 0) { initSinglePageViewers(); } }, INIT_DELAY); }, INIT_DELAY); }); }

// Optimized: Lock/unlock containers function lockContainersInPlace() { const contentArea = getContentArea(); if (!contentArea) return;

const containers = getContainers(true); containerPositions.clear();

requestAnimationFrame(function() { containers.forEach(function(container) { if (container.getAttribute('data-position-locked') !== 'true') { const rect = container.getBoundingClientRect(); Object.assign(container.style, { position: 'fixed', top: rect.top + 'px', left: rect.left + 'px', width: rect.width + 'px', zIndex: '9999', margin: '0' }); container.setAttribute('data-position-locked', 'true'); containerPositions.set(container, { top: rect.top, left: rect.left, width: rect.width, height: rect.height }); } }); }); }

function unlockContainers() { const contentArea = getContentArea(); if (!contentArea) return;

const containers = Array.from( contentArea.querySelectorAll('.single-page-viewer-container[data-position-locked="true"]') );

containers.forEach(function(container) { Object.assign(container.style, { position: '', top: '', left: '', width: '', zIndex: '', margin: '' }); container.removeAttribute('data-position-locked'); // Clean up container position from Map to prevent memory leak containerPositions.delete(container); });

containerPositions.clear(); }

// Optimized: Reposition containers with better DOM operations // Core repositioning function (not debounced for immediate use) function repositionContainersAfterThemeToggleCore() { const contentArea = getContentArea(); if (!contentArea) return;

const containers = getContainers(true);

containers.forEach(function(container) { const viewerId = container.getAttribute('data-viewer-id'); if (!viewerId) return;

let viewer = document.getElementById(viewerId);

if (!viewer) { const iframe = container.querySelector('iframe'); if (iframe && iframe.src) { const allViewers = contentArea.querySelectorAll('[data-single-page-viewer]'); for (let i = 0; i < allViewers.length; i++) { const v = allViewers[i]; if (v.getAttribute('data-src') === iframe.src) { viewer = v; viewer.id = viewerId; viewer.setAttribute('data-viewer-id', viewerId); Object.assign(viewer.style, { display: 'none', visibility: 'hidden', position: 'absolute', width: '1px', height: '1px', overflow: 'hidden', opacity: '0', pointerEvents: 'none', margin: '0', padding: '0' }); viewer.setAttribute('data-initialized', 'true'); viewer.setAttribute('data-viewer-replaced', 'true'); break; } } } }

if (viewer && viewer.parentNode) { if (container.getAttribute('data-position-locked') === 'true') { unlockContainers(); }

const viewerIndex = Array.from(viewer.parentNode.children).indexOf(viewer); const containerIndex = Array.from(viewer.parentNode.children).indexOf(container);

if (containerIndex !== viewerIndex + 1) { // CRITICAL: Check if container is actually a child before removing if (container.parentNode && container.parentNode.contains(container)) { try { container.parentNode.removeChild(container); } catch (e) { return; // Skip repositioning if removal failed } } if (viewer.nextSibling) { viewer.parentNode.insertBefore(container, viewer.nextSibling); } else { viewer.parentNode.appendChild(container); } } else if (container.parentNode !== viewer.parentNode) { // CRITICAL: Check if container is actually a child before removing if (container.parentNode && container.parentNode.contains(container)) { try { container.parentNode.removeChild(container); } catch (e) { return; // Skip repositioning if removal failed } } if (viewer.nextSibling) { viewer.parentNode.insertBefore(container, viewer.nextSibling); } else { viewer.parentNode.appendChild(container); } } } });

invalidateQueryCache(); }

// Debounced version for non-critical repositioning const repositionContainersAfterThemeToggle = debounce(repositionContainersAfterThemeToggleCore, REPOSITION_DEBOUNCE);

// Optimized: Observer setup with better filtering let observer = null; let containerPositionObserver = null; let fallbackObserver = null; // Track fallback observer for cleanup

function setupContainerPositionObserver() { if (typeof MutationObserver === 'undefined') return;

const contentArea = getContentArea(); if (!contentArea || !document.contains(contentArea)) { setTimeout(setupContainerPositionObserver, 500); return; }

if (containerPositionObserver) { containerPositionObserver.disconnect(); }

containerPositionObserver = new MutationObserver(function(mutations) { if (themeToggleInProgress) { // Use core function (not debounced) for immediate repositioning during theme toggle requestAnimationFrame(repositionContainersAfterThemeToggleCore); return; }

let needsReposition = false; for (let i = 0; i < mutations.length && !needsReposition; i++) { if (mutations[i].type === 'childList') { needsReposition = true; } }

if (needsReposition) { clearTimeout(containerPositionObserver._timeout); containerPositionObserver._timeout = setTimeout(repositionContainersAfterThemeToggle, REPOSITION_DEBOUNCE); } });

containerPositionObserver.observe(contentArea, { childList: true, subtree: true, attributes: false, characterData: false });

containerPositionObserver._timeout = null; }

// Optimized: Main observer with better early exits function setupObserver() { if (typeof MutationObserver === 'undefined') return;

const contentArea = getContentArea(); if (!contentArea || !document.contains(contentArea)) { setTimeout(setupObserver, 500); return; }

if (contentArea === document.documentElement || contentArea === document.body) { return; }

if (observer) { const currentObserved = observer._observedElement; if (currentObserved === contentArea && document.contains(contentArea)) { return; } if (currentObserved) { observer.disconnect(); } observer = null; }

observer = new MutationObserver(function(mutations) { if (themeToggleInProgress) return;

let hasNewViewer = false; for (let i = 0; i < mutations.length && !hasNewViewer; i++) { const mutation = mutations[i]; if (mutation.addedNodes && mutation.addedNodes.length > 0) { for (let j = 0; j < mutation.addedNodes.length && !hasNewViewer; j++) { const node = mutation.addedNodes[j]; if (node.nodeType === 1 && node.hasAttribute && node.hasAttribute('data-single-page-viewer') && !node.hasAttribute('data-initialized') && !node.hasAttribute('data-viewer-replaced')) { hasNewViewer = true; } } } }

if (hasNewViewer) { // Use core function for immediate repositioning when new viewer detected repositionContainersAfterThemeToggleCore(); setTimeout(repositionContainersAfterThemeToggleCore, 10); setTimeout(repositionContainersAfterThemeToggleCore, 50); setTimeout(repositionContainersAfterThemeToggleCore, 200); }

clearTimeout(observerTimeout); observerTimeout = setTimeout(function() { if (themeToggleInProgress) return;

let hasNewViewer = false; for (let i = 0; i < mutations.length && !hasNewViewer; i++) { const mutation = mutations[i];

if (mutation.target === document.documentElement || mutation.target === document.body) { continue; }

if (mutation.type === 'attributes' && mutation.attributeName === 'class' && mutation.target === document.documentElement) { continue; }

if (mutation.type === 'attributes' && mutation.attributeName === 'data-initialized') { const target = mutation.target; if (target && target.hasAttribute && target.hasAttribute('data-single-page-viewer') && !target.hasAttribute('data-initialized')) { hasNewViewer = true; } }

if (mutation.addedNodes && mutation.addedNodes.length > 0) { for (let j = 0; j < mutation.addedNodes.length && !hasNewViewer; j++) { const node = mutation.addedNodes[j]; if (node.nodeType === 1 && node.hasAttribute && node.hasAttribute('data-single-page-viewer') && !node.hasAttribute('data-initialized')) { hasNewViewer = true; } } } }

if (hasNewViewer) { invalidateQueryCache(); initSinglePageViewers(); } }, OBSERVER_DEBOUNCE); });

observer.observe(contentArea, { childList: true, subtree: true, attributes: true, attributeFilter: ['data-initialized'], characterData: false });

observer._observedElement = contentArea; setupContainerPositionObserver(); }

function start() { setupObserver(); initialize(); }

function checkAndInitialize() { if (isInitializing) return;

const currentUrl = window.location.href; if (currentUrl === lastUrl && lastUrl !== null) { return; }

const contentArea = document.querySelector('#content-area') || document.querySelector('.mdx-content'); if (contentArea) { const viewers = getUninitializedViewers(true); if (viewers.length > 0) { cachedContentArea = contentArea; setupObserver(); initialize(); } } }

if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', start); } else { setTimeout(start, 100); }

if (typeof window !== 'undefined' && window.next) { let initialLoadAttempts = 0; const maxInitialAttempts = 4; const attemptDelays = [200, 500, 1000, 2000]; const initialUrl = window.location.href;

attemptDelays.forEach(function(delay) { setTimeout(function() { if (initialLoadAttempts < maxInitialAttempts) { initialLoadAttempts++; if (window.location.href === initialUrl) { checkAndInitialize(); } } }, delay); }); }

window.addEventListener('popstate', function(e) { const currentUrl = window.location.href; if (currentUrl !== lastUrl) { handleNavigation(); } });

if (typeof window !== 'undefined') { if (window.next && window.next.router && window.next.router.events) { window.next.router.events.on('routeChangeComplete', function(url) { lastUrl = window.location.href; isInitializing = false; pendingInitCall = false; if (navigationTimeout) { clearTimeout(navigationTimeout); navigationTimeout = null; } invalidateContentAreaCache(); invalidateQueryCache(); setTimeout(function() { setupObserver(); initialize(); }, 300); });

window.next.router.events.on('routeChangeStart', function(url) { isInitializing = false; pendingInitCall = false; }); }

if (!window.next || !window.next.router) { const originalPushState = history.pushState; const originalReplaceState = history.replaceState;

history.pushState = function() { originalPushState.apply(history, arguments); setTimeout(function() { if (window.location.href !== lastUrl) { handleNavigation(); } }, 0); };

history.replaceState = function() { originalReplaceState.apply(history, arguments); setTimeout(function() { if (window.location.href !== lastUrl) { handleNavigation(); } }, 0); }; } }

document.addEventListener('visibilitychange', function() { if (!document.hidden) { isInitializing = false; startPeriodicCheck(); if (!pendingInitCall) { pendingInitCall = true; setTimeout(function() { pendingInitCall = false; initSinglePageViewers(); }, INIT_DELAY / 2); } } });

function startPeriodicCheck() { if (periodicCheckInterval) return;

periodicCheckInterval = setInterval(function() { const uninitialized = getUninitializedViewers(true); if (uninitialized.length === 0) { clearInterval(periodicCheckInterval); periodicCheckInterval = null; } else if (!isInitializing) { initSinglePageViewers(); } }, 2000); }

setTimeout(startPeriodicCheck, INIT_DELAY * 2);

window.__singlePageViewerInit = function() { const currentUrl = window.location.href; if (currentUrl === lastUrl) { return; }

isInitializing = false; pendingInitCall = false; lastUrl = currentUrl; invalidateContentAreaCache(); invalidateQueryCache(); setupObserver(); initialize(); };

window.__singlePageViewerCheck = function() { invalidateQueryCache(); initSinglePageViewers(); }; })();

// Optimized: Second IIFE for navigation handling (function() { 'use strict';

let lastPathname = window.location.pathname; let lastInitializedPathname = window.location.pathname;

function checkAndInit() { const currentPathname = window.location.pathname; if (currentPathname === lastInitializedPathname) { return; }

lastInitializedPathname = currentPathname; if (typeof window !== 'undefined') { window.__lastFullUrl = window.location.href; }

if (window.__singlePageViewerInit) { window.__singlePageViewerInit(); } }

function runInit() { if (window.__singlePageViewerInit) { setTimeout(checkAndInit, 100); } else { let attempts = 0; const maxAttempts = 50; const checkInterval = setInterval(function() { attempts++; if (window.__singlePageViewerInit) { clearInterval(checkInterval); setTimeout(checkAndInit, 100); } else if (attempts >= maxAttempts) { clearInterval(checkInterval); } }, 100); } }

if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', runInit); } else { setTimeout(runInit, 100); }

if (typeof window !== 'undefined' && window.next && window.next.router && window.next.router.events) { window.next.router.events.on('routeChangeComplete', function(url) { lastPathname = window.location.pathname; setTimeout(checkAndInit, 300); }); }

setInterval(function() { const currentPathname = window.location.pathname; if (currentPathname !== lastPathname) { lastPathname = currentPathname; setTimeout(checkAndInit, 300); } }, 500);

if (typeof MutationObserver !== 'undefined') { let fallbackObserverTimeout = null; // Store in outer scope for cleanup tracking fallbackObserver = new MutationObserver(function(mutations) { if (fallbackObserverTimeout) { clearTimeout(fallbackObserverTimeout); } fallbackObserverTimeout = setTimeout(function() { const contentArea = document.querySelector('#content-area'); if (contentArea) { const uninitialized = contentArea.querySelectorAll( '[data-single-page-viewer]:not([data-initialized]):not(.single-page-viewer-container [data-single-page-viewer])' ); if (uninitialized.length > 0) { const currentPathname = window.location.pathname; if (currentPathname !== lastInitializedPathname) { setTimeout(checkAndInit, 200); } } } }, 500); });

if (document.body) { fallbackObserver.observe(document.body, { childList: true, subtree: true }); } else { document.addEventListener('DOMContentLoaded', function() { if (document.body) { fallbackObserver.observe(document.body, { childList: true, subtree: true }); } }); } } })();