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

# Impersonate User

The `impersonateUser` mutation exchanges a permanent access key for a short-lived JWT token that authenticates subsequent requests as a specific user. This is the core of the embedded session flow — the returned JWT is passed as the `token` query parameter in iframe embed URLs.

This mutation is public and unauthenticated — no `Authorization` header is required.

## Signature

```graphql theme={null}
impersonateUser(accessToken: String!, userEmail: String!, attributes: [UserAttributeInput!]): String!
```

## Parameters

<ParamField path="accessToken" type="String!" required>
  Your permanent Descope access key. This is not a JWT or a prior session token — it is the long-lived key obtained from `support@askwisdom.ai`. Must be kept secret and only used server-side.
</ParamField>

<ParamField path="userEmail" type="String!" required>
  The email address of the user to impersonate. The user must already exist in Wisdom — call [`createUsers`](/integrations/graphql-api/mutations/user/create-users) first if needed.
</ParamField>

<ParamField path="attributes" type="[UserAttributeInput!]">
  Optional key-value pairs applied **only for this session**. These override DATABASE-sourced attributes for the duration of the session and are not persisted. Useful for passing context that should not be stored on the user (e.g., a specific report context or request-scoped filter).

  <Warning>
    The `impersonated_user_id` claim cannot be overridden via attributes.
  </Warning>

  See [User Attributes](/integrations/user-management/user-attributes) for how attribute sources are prioritized.
</ParamField>

## Response

Returns a raw `String!` — the short-lived JWT to use as the `token` query parameter in iframe embed URLs. The token expires after **1 hour**.

<Warning>
  Never expose your permanent access key in client-side code. Always call this mutation from your server. See [Embed a Chat and Dashboards](/integrations/embeddings/embedding) for the secure server-side flow.
</Warning>

## Usage example

```graphql theme={null}
mutation ImpersonateUser($accessToken: String!, $userEmail: String!) {
  impersonateUser(accessToken: $accessToken, userEmail: $userEmail)
}
```

<RequestExample>
  ```bash Without attributes theme={null}
  curl -X POST \
    -H "Content-Type: application/json" \
    -d '{
      "query": "mutation ImpersonateUser($accessToken: String!, $userEmail: String!) { impersonateUser(accessToken: $accessToken, userEmail: $userEmail) }",
      "variables": {
        "accessToken": "<your_access_key>",
        "userEmail": "alice@yourcompany.com"
      }
    }' \
    https://{ACCOUNT}.askwisdom.ai/graphql
  ```

  ```bash With transient attributes theme={null}
  curl -X POST \
    -H "Content-Type: application/json" \
    -d '{
      "query": "mutation ImpersonateUser($accessToken: String!, $userEmail: String!, $attributes: [UserAttributeInput!]) { impersonateUser(accessToken: $accessToken, userEmail: $userEmail, attributes: $attributes) }",
      "variables": {
        "accessToken": "<your_access_key>",
        "userEmail": "alice@yourcompany.com",
        "attributes": [
          { "key": "report_id", "value": "q3-sales-summary" },
          { "key": "customer_id", "value": "cust_789" }
        ]
      }
    }' \
    https://{ACCOUNT}.askwisdom.ai/graphql
  ```
</RequestExample>

<ResponseExample>
  ```json Response theme={null}
  {
    "data": {
      "impersonateUser": "eyJhbGciOiJSUzI1NiIsInR5..."
    }
  }
  ```
</ResponseExample>

<Tip>
  Attributes that are stable for a given user (such as `account_id`) should be set permanently via [`createUsers`](/integrations/graphql-api/mutations/user/create-users) or [`setUserAttributes`](/integrations/graphql-api/mutations/user/set-user-attributes). Attributes that change per session (such as a selected view or context) can be passed transiently via `attributes` here.
</Tip>

## Token lifecycle and refresh

| Property | Value                                                             |
| -------- | ----------------------------------------------------------------- |
| Lifetime | 1 hour                                                            |
| Refresh  | Via `postMessage` (see below)                                     |
| Priority | `?token=` URL param always takes precedence over any cached token |

### Built-in refresh via `postMessage`

WisdomAI sends a `REQUEST_JWT_TOKEN` event to the parent window approximately 10 seconds before the embedded token expires. It is the **embedder's responsibility** to listen for this event, regenerate the token by calling `impersonateUser` from their backend, and post it back. WisdomAI then picks up the new token and continues the session seamlessly.

**Flow:**

1. WisdomAI iframe detects the token is about to expire
2. WisdomAI sends `window.parent.postMessage({ type: 'REQUEST_JWT_TOKEN' }, '*')` to the host
3. Host receives the event and validates `event.origin` matches the WisdomAI domain
4. Host calls `impersonateUser` via its own backend to get a fresh JWT
5. Host posts back `iframe.contentWindow.postMessage({ type: 'JWT_TOKEN_RESPONSE', token }, WISDOM_ORIGIN)`
6. WisdomAI receives the new token and refreshes the session

```html theme={null}
<iframe
  id="wisdom-frame"
  src="https://{ACCOUNT}.askwisdom.ai/embed/dashboards/<id>?token=<JWT>"
  style="width:100%;height:800px;border:0"
></iframe>

<script>
  const iframe = document.getElementById('wisdom-frame');
  const WISDOM_ORIGIN = 'https://{ACCOUNT}.askwisdom.ai';

  async function getFreshWisdomToken() {
    // Call your own backend, which calls impersonateUser with the access key
    const response = await fetch('/api/wisdom-token');
    const data = await response.json();
    return data.token;
  }

  window.addEventListener('message', async (event) => {
    if (event.origin !== WISDOM_ORIGIN) return;
    if (event.source !== iframe.contentWindow) return;
    if (event.data?.type !== 'REQUEST_JWT_TOKEN') return;

    try {
      const token = await getFreshWisdomToken();
      iframe.contentWindow.postMessage(
        { type: 'JWT_TOKEN_RESPONSE', token },
        WISDOM_ORIGIN
      );
    } catch (error) {
      console.error('Unable to provide WisdomAI token', error);
    }
  });
</script>
```

**Implementation notes:**

* WisdomAI sends with `'*'` as target origin — the host **must** validate `event.origin` on its side
* If the host does not respond within **10 seconds**, the request times out and retries on the next render cycle
* Only one refresh request is in-flight at a time (duplicate requests are suppressed)
* The `?token=` URL param always takes priority over any cached token

## Related articles

<CardGroup cols={2}>
  <Card title="Embed a Dashboard" icon="chart-bar" href="/integrations/embeddings/iframe/embed-a-dashboard">
    Use the token in an iframe embed URL
  </Card>

  <Card title="Embed a Chat" icon="message" href="/integrations/embeddings/iframe/embed-chat">
    Use the token for embedded chat
  </Card>

  <Card title="Create Users" icon="user-plus" href="/integrations/graphql-api/mutations/user/create-users">
    Create users before impersonating them
  </Card>

  <Card title="User Attributes" icon="list" href="/integrations/user-management/user-attributes">
    Understand transient vs persistent attributes
  </Card>

  <Card title="Session Management" icon="clock" href="/integrations/embeddings/session-management">
    Full token lifecycle reference, including switching users
  </Card>
</CardGroup>
