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.
Overview
ATG Embedded runs inside an isolated iframe hosted on app.askthisguy.com, ensuring data privacy, robust security, and effortless updates. 🔒
Integration modes
| Mode | Typical Use Case | Authentication to scope the session | Requires Backend |
|---|---|---|---|
| Mode 1 - Public | Website FAQ, lead gen, simple SaaS help without per-user context | None | No |
| Mode 2 - Connected | Auth'd SaaS, contextual support, automations | Server-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
kidheader
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 ⚡
-
Verifies signature using the agent's public key (identified by kid)
-
Validates claims:
- Timestamps (iat, exp) - max 15-minute lifetime
- jti uniqueness - prevents replay attacks (stored for 15 min)
-
Issues DPoP-bound access token - cryptographically bound to the iframe's ephemeral key pair
-
Provides session context - optional claims (external_user_ref, entitlements, context_metadata) are available to the agent for personalization
Appendix 📚
Widget parameters
| Attribute | Description |
|---|---|
data-agent-id | Required. Agent ID provided by ATG. |
data-signed-params | Optional. Boolean (true or false). Enables signed parameter authentication for enhanced security. Requires implementing window.ATG.getSignedParams callback. Defaults to false. |
data-launcher | Optional. Position of the chat launcher (bottom-right, bottom-left, floating, hidden). Defaults to bottom-right. |
data-language | Optional. Interface language en, fr. Defaults to en. |
data-page-route | Optional. 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-json | Optional. 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-label | Optional. 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 name | Description | Example value |
|---|---|---|
color.brand | Primary brand color | #6D28D9 |
color.bg | Chat background color | #0B0B0F |
color.text | Main text color | #F3F4F6 |
color.muted | Secondary text color | #9CA3AF |
radius.sm, radius.lg | Corner radius for buttons, cards | 8px, 16px |
font.family | Font stack | Inter, system-ui, sans-serif |
shadow.elevation | Card/launcher shadow | 0 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
| Claim | Type | Description | Example / Notes |
|---|---|---|---|
kid | string | Key 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" |
alg | string | Algorithm: Must be "ES256" (ECDSA with P-256 curve and SHA-256). | "ES256" |
Payload claims (required)
| Claim | Type | Description | Example / Notes |
|---|---|---|---|
iat | number (Unix seconds) | Issued-at time: when the token was generated. Used to detect clock drift or replay. | 1739298000 |
exp | number (Unix seconds) | Expiration: absolute expiry of this Signed Params token. Must be short (≤ 15 min). ATG refuses expired or overly long-lived tokens. | 1739298900 |
jti | string (UUID/random) | JWT ID: unique nonce for this token. Must be random for each issuance. | 550e8400-e29b-41d4-a716-446655440000 |
Payload claims (optional)
| Claim | Type | Description | Example / Notes |
|---|---|---|---|
external_user_ref | string | External user reference or ID from your system. Can be used to identify the user in agent instructions or logs. | "user_12345" |
entitlements | object (JSON) | User entitlements or permissions. Can be used by agent for access control logic. | {"plan": "premium", "features": ["export"]} |
context_metadata | object (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:
-
Timer Setup: When a session is initialized, a rotation timer is scheduled based on the expires_in value (typically 10 minutes).
-
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:
- Generate a new signing keypair and give it a new
kid(e.g.key-2025-02). - Add the new public key to ATG console while keeping the old one until all tokens signed with it expire (≤ 15 min).
- 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. ✨