Integrate ATG Embedded in your webapp

Technical guide to integrate ATG Embedded in your webapp: iframe isolation, integration modes (public vs connected), widget, authentication, and step-by-step integration.

6 min read

Overview

ATG Embedded runs inside an isolated iframe hosted on app.askthisguy.com, ensuring data privacy, robust security, and effortless updates. 🔒

Integration modes

ModeTypical Use CaseAuthentication to scope the sessionRequires Backend
Mode 1 - PublicWebsite FAQ, lead gen, simple SaaS help without per-user contextNoneNo
Mode 2 - ConnectedAuth'd SaaS, contextual support, automationsServer-signed JWT with short TTL✅ Yes

Key concepts 🔑

For both modes

Widget (widget.js)

A lightweight script that ATG provides and that you embed in your site.

It injects an iframe pointing to https://app.askthisguy.com, creates the chat launcher via Shadow DOM, and communicates with the iframe using postMessage.

Iframe isolation

All chat UI and data live inside a cross-origin iframe (app.askthisguy.com) under the browser's Same-Origin Policy:

  • Your page cannot read ATG tokens
  • ATG cannot access your cookies or scripts

Shadow DOM

Prevents CSS/JS collisions with your site.

Access token

To maintan the chat sessions with users, ATG ensures that the iframe holds a short-lived, DPoP-bound bearer token in memory and sends it as Authorization: DPoP \<access\> with the right header.

This mechanism allows to maintain a session on browser like Safari where 3rd party cookies and partitionned cookies would get blocked.

For Mode 2 only

Signed Parameters

The only backend work that is required consists in developing a simple endpoint, that will create a Json Web Token (JWT) signed with your private key.

This is an example JWT with the minimum required parameter :

  // Header
  {
    "alg": "ES256",
    "kid": "prod-key-2024-01"
  }

  // Payload (minimal required)
  {
    "iat": 1739298000,
    "exp": 1739298900,
    "jti": "550e8400-e29b-41d4-a716-446655440000"
  }

You'll be able to upload a public key in ATG's administration console so that ATG can decode the JWT and open a chat session with the user. You keep the private key on your servers.

You can pass on more parameters to ATG that define the user’s entitlements and context for this session. Check the appendix for the description of all supported parameters.

Architecture overview

Mode 1 - Public flow

Loading diagram…

Mode 2 - Connected

Loading diagram…

Once the session is established, the widget streams user messages to ATG over a secure channel (SSE/HTTPS), and ATG streams assistant tokens back in real time to the iframe, which renders the chat UI.

Loading diagram…

Integration Steps (both modes)

Step 1: Configure your agent in ATG's admin console ⚙️

Create your agent and set up the knowledge

First, set up your agent in the admin console with your instructions and knowledge.

See Configure Agents for more details. This is very straightforward and generally done by the functional team / business users. They will also allow you to test the agent's behavior in the playground integrated in the admin console.

Allow-Listed Origins

In the admin console, provide ATG with the exact origins authorized to embed the widget (e.g., https://yourapp.com, https://staging.yourapp.com)

Then, it will be automatically enforced via both runtime checks and CSP (Content Security Policy) headers on the iframe, ensuring the chat can only be embedded on approved domains.

ATG will set its CSP (Content-Service-Policy) with a frame-ancestors directive based on your allow-listed origins:

Content-Security-Policy:
  frame-ancestors [https://yourapp.com](https://yourapp.com) [https://staging.yourapp.com](https://staging.yourapp.com);

Step 2: Check that your website does not block ATG

If your website uses a strict Content-Security-Policy (CSP) that blocks iframes or external scripts by default, you need to allow the ATG widget and iframe explicitly.

To check if your website uses CSP, you can use a curlcommand for example:

curl -I [https://yourwebsite.com](https://yourwebsite.com) | grep -i content-security-policy

If it returns nothing or only frame-ancestors rule (most common case), then you do not need to extend the CSP header, jump to next step

If it returns strict CSPs, like Content-Security-Policy: default-src 'self'; script-src 'self'; frame-src 'none';, then add or extend your CSP header as follows:

Content-Security-Policy:
  script-src 'self' [https://app.askthisguy.com](https://app.askthisguy.com);
  frame-src [https://app.askthisguy.com](https://app.askthisguy.com);

This header is set on your own server or CDN (by your tech/ops team).

It tells browsers your page is allowed to load the ATG script and iframe.

Step 3: Add the Widget script 💻

You will find a simple JS code in ATG’s admin console you can add near the end of the \<body\> of each page. It will look like:

  <!-- Load jose library -->
  <script type="module">
    import * as jose from 'https://cdn.jsdelivr.net/npm/jose@5.2.3/+esm';
    window.jose = jose;
  </script>

  <!-- Load widget (no signed params) -->
  <script async
    src="https://app.askthisguy.com/widget.js"
    data-agent-id="YOUR_AGENT_ID"
    data-language="fr"
    data-launcher="bottom-right">
  </script>

Check the Appendix for the detailed description of all the parameters supported by the widget.

Additional integration steps for Mode 2 🔐

1. Implement /embed/presign 🛠️

To enable Mode 2, you will need to implement one simple end-point /embed/presign. You can change this name if you prefer to.

This end-point must:

  • Be a POST end-point
  • Verify that the user is authenticated in your app
  • If so, return a short lived Signed Parameters JWT with a TTL < 15 min
  • It must be signed using ES256 ****and include a kid header

We also recommend applying same-origin only on /embed/presign

Response payload :

  // Payload (with optional context)
  {
    "iat": 1739298000,
    "exp": 1739298900,
    "jti": "550e8400-e29b-41d4-a716-446655440000",
    "external_user_ref": "user_12345",
    "entitlements": {
      "plan": "premium",
      "features": ["export", "analytics"]
    },
    "context_metadata": {
      "department": "sales",
      "region": "EU"
    }
  }

Check the appendix for the description of all supported parameters.

2. Configure the public key for ATG to decode the JWT 🔑

In the admin console of ATG, go to your agent's security settings and enter the public key that ATG can use to decode the JWT token that will be signed by your private key in /embed/presign

3. Provide the JWT to the widget 📤

The JS code to append to you website will be slightly different for Mode 2.

From the admin console, you can download the latest JS code to insert to your page or via your tag manager. The code will look like:

<!-- STEP 1: Load jose library (required by widget for JWT signing) -->
  <script type="module">
    import * as jose from 'https://cdn.jsdelivr.net/npm/jose@5.2.3/+esm';
    window.jose = jose;
  </script>

  <!-- STEP 2: Implement getSignedParams callback (called by widget for
  session init & rotation) -->
  <script>
    window.ATG = window.ATG || {};

    // Called by widget when it needs fresh Signed Params (init & refresh)
    window.ATG.getSignedParams = async () => {
      try {
        // Call your backend presign endpoint
        const response = await fetch("/embed/presign", {
          method: "POST",
          credentials: "include" // Include session cookies for user authentication
        });

        if (!response.ok) {
          throw new Error(`Presign failed: ${response.status}`);
        }

        const data = await response.json();

        // Return the JWT string (not the object)
        // Your backend should return: { "signed_jwt": "eyJ..." }
        return data.signed_jwt;

      } catch (error) {
        console.error("Failed to get signed params:", error);
        throw error; // Let ATG widget handle the error
      }
    };
  </script>

  <!-- STEP 3: Load ATG widget with configuration -->
  <script async
    src="https://app.askthisguy.com/widget.js"
    data-agent-id="YOUR_AGENT_ID"
    data-signed-params="true"
    data-language="fr"
    data-launcher="bottom-right"
    data-page-route="/plans"
    data-theme-json='{"color.brand":"#6D28D9"}'
    data-aria-label="Ouvrir le chat d'assistance">
  </script>

The widget will call window.ATG.getSignedParams() to obtain the JWT

Feel free to adapt and integrate this code according to your security standards:

  • Make sure you provide window.ATG.getSignedParams()
  • Define once; guard against re-injection in SPAs
  • Prefer CSP nonce

If you're using a tag manager and you've defined two tags, one for the call-back and one for the widget.js, please make sure you use tag sequencing with the call-back loading first.

4. What ATG does with the Signed Parameters ⚡

  1. Verifies signature using the agent's public key (identified by kid)

  2. Validates claims:

  • Timestamps (iat, exp) - max 15-minute lifetime
  • jti uniqueness - prevents replay attacks (stored for 15 min)
  1. Issues DPoP-bound access token - cryptographically bound to the iframe's ephemeral key pair

  2. Provides session context - optional claims (external_user_ref, entitlements, context_metadata) are available to the agent for personalization

Appendix 📚

Widget parameters

AttributeDescription
data-agent-idRequired. Agent ID provided by ATG.
data-signed-paramsOptional. Boolean (true or false). Enables signed parameter authentication for enhanced security. Requires implementing window.ATG.getSignedParams callback. Defaults to false.
data-launcherOptional. Position of the chat launcher (bottom-right, bottom-left, floating, hidden). Defaults to bottom-right.
data-languageOptional. Interface language en, fr. Defaults to en.
data-page-routeOptional. Canonical route for the current page (e.g. /plans). Useful if you set up agent instructions that take it in account for contextual content.
data-theme-jsonOptional. JSON object defining custom visual tokens such as brand color, fonts or corner radius. It helps instantly style the assistant. Example: {"color.brand":"#6D28D9","radius.lg":"12px"}
data-aria-labelOptional. Provides an accessibility label for the chat launcher button, allowing screen readers to announce its purpose (e.g. "Open chat support assistant").

data-theme-json description:

Token nameDescriptionExample value
color.brandPrimary brand color#6D28D9
color.bgChat background color#0B0B0F
color.textMain text color#F3F4F6
color.mutedSecondary text color#9CA3AF
radius.sm, radius.lgCorner radius for buttons, cards8px, 16px
font.familyFont stackInter, system-ui, sans-serif
shadow.elevationCard/launcher shadow0 8px 24px rgba(0,0,0,0.25)

End-point parameters

The JWT signed by /embed/presign to start a session must have two parts: header and claims (payload).

Header

ClaimTypeDescriptionExample / Notes
kidstringKey ID: Identifies which public key to use for signature verification. Must match a key registered with your agent in ATG's admin console."prod-key-2024-01"
algstringAlgorithm: Must be "ES256" (ECDSA with P-256 curve and SHA-256)."ES256"

Payload claims (required)

ClaimTypeDescriptionExample / Notes
iatnumber (Unix seconds)Issued-at time: when the token was generated. Used to detect clock drift or replay.1739298000
expnumber (Unix seconds)Expiration: absolute expiry of this Signed Params token. Must be short (≤ 15 min). ATG refuses expired or overly long-lived tokens.1739298900
jtistring (UUID/random)JWT ID: unique nonce for this token. Must be random for each issuance.550e8400-e29b-41d4-a716-446655440000

Payload claims (optional)

ClaimTypeDescriptionExample / Notes
external_user_refstringExternal user reference or ID from your system. Can be used to identify the user in agent instructions or logs."user_12345"
entitlementsobject (JSON)User entitlements or permissions. Can be used by agent for access control logic.{"plan": "premium", "features": ["export"]}
context_metadataobject (JSON)Additional context metadata about the session or user.{"department": "sales", "region": "EU"}

FAQ ❓

ATG automatically rotates the session token 60 seconds before expiration to ensure uninterrupted chat:

  1. Timer Setup: When a session is initialized, a rotation timer is scheduled based on the expires_in value (typically 10 minutes).

  2. Pre-Rotation Request (if signed parameters enabled):

  • 60 seconds before token expires, the iframe sends a postMessage to the widget: { type: "ATG_REQUEST_SIGNED_PARAMS_FOR_ROTATION" }
  • Widget automatically calls your window.ATG.getSignedParams() callback to fetch a fresh signed JWT.
  • Your callback should call your backend's presign endpoint and return the new JWT.
  • Widget responds to iframe with the JWT and the iFrame rotates the session with ATG's server.

Each Signed Parameters JWT includes a kid (Key ID) in its header.

ATG uses that value to instantly find the right public key you've uploaded in the console.

When you rotate keys:

  1. Generate a new signing keypair and give it a new kid (e.g. key-2025-02).
  2. Add the new public key to ATG console while keeping the old one until all tokens signed with it expire (≤ 15 min).
  3. Start signing new tokens with the new kid, then safely remove the old key.

ATG automatically verifies each token against the public key that matches its kid, so rotation happens seamlessly, no downtime or manual reconfiguration. ✨