How to embed AnnA Chat with a Corporate User V3
This guide shows how to embed AnnA Chat for an authenticated Corporate User using the V3 API. The browser receives only the generated AnnA Chat URL. Keep the company hash, user credentials, bearer token, and all V3 API calls on your backend.
Before you start
Section titled “Before you start”You need the following information from your AnnA environment:
- The company hash.
- The login and password used with
REST/Credentials/GetToken. - The Corporate User ID and, optionally, the user name.
- The base URL of your AnnA environment.
V3 uses token authentication instead of the encryption keys required by V2. The token is short-lived and must never be exposed in the client-side page.
Integration flow
Section titled “Integration flow”- Your backend calls
REST/Credentials/GetTokenwith the company hash and service credentials. - Your backend uses the returned token in an
Authorization: Bearer <Token>header. - Your backend calls
REST/AnnACorporate/User/UserHashorREST/AnnACorporate/User/UserHashForcedwithgetAnnAChatLinkset totrue. - Your backend reads
accessData.AnnAChatURLfrom the response. - Your client-side code uses that generated URL as the iframe source.
Use UserHash when the Corporate User already exists. Use UserHashForced when AnnA should create or update the user with the supplied userName before generating the hash.
The generated URL can expire. Generate it on the server for the signed-in user and do not put the company hash, login, password, or bearer token in browser code.
Get a chat URL
Section titled “Get a chat URL”First request a token:
Endpoint: REST/Credentials/GetToken
{ "hash": "YOUR_COMPANY_HASH", "user": "YOUR_API_USER", "pass": "YOUR_API_PASSWORD"}Then call the Corporate User endpoint with the token:
Endpoint: REST/AnnACorporate/User/UserHash
{ "getAnnAChatLink": true, "hash": "YOUR_COMPANY_HASH", "userId": "USER_ID", "startService": ""}For a user that may need to be created or updated, use REST/AnnACorporate/User/UserHashForced and add the userName property:
{ "getAnnAChatLink": true, "hash": "YOUR_COMPANY_HASH", "userId": "USER_ID", "userName": "USER_NAME", "startService": ""}Response from AnnA
Section titled “Response from AnnA”The successful response contains the URL in accessData.AnnAChatURL:
{ "success": true, "message": "User Hash generated successfully", "accessData": { "Hash": "USER_HASH", "ChatURL": "CHAT_URL_WITH_USER_HASH", "EmbeddedURL": "EMBEDDED_URL_WITH_USER_HASH", "AnnAChatURL": "ANNACHAT_URL_FOR_IFRAME", "Expires": "EXPIRATION_DATETIME" }}If success is false, handle the message value on the backend and do not send an invalid URL to the browser.
Complete example
Section titled “Complete example”Replace ANNACHAT_URL_FROM_BACKEND with the accessData.AnnAChatURL value returned by your backend. The launcher creates the chat window only after the user clicks it.
<!doctype html><html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>AnnA Chat Embedded Demo</title> </head> <body> <h1>Corporate client site</h1> <p>The authenticated AnnA Chat widget appears in the lower-right corner.</p>
<script> (function () { const ANNA_URL = "ANNACHAT_URL_FROM_BACKEND"; // Use the backend-generated AnnAChatURL value const CHAT_W = 400, CHAT_H = 600, BTN_SIZE = 60; const sideOffset = "clamp(16px, 2.2vw, 30px)"; const bottomOffset = "clamp(16px, 2.2vw, 30px)";
const btn = document.createElement("div"); btn.id = "anna-launcher"; Object.assign(btn.style, { position: "fixed", bottom: bottomOffset, right: sideOffset, width: BTN_SIZE + "px", height: BTN_SIZE + "px", borderRadius: "50%", cursor: "pointer", zIndex: "9999", background: "#7c3aed", display: "flex", alignItems: "center", justifyContent: "center", boxShadow: "0 4px 12px rgba(0,0,0,0.25)", transition: "transform 0.2s", }); btn.innerHTML = '<svg width="28" height="28" fill="#fff" viewBox="0 0 24 24"><path d="M20 2H4a2 2 0 0 0-2 2v18l4-4h14a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2z"/></svg>'; btn.onmouseenter = function () { btn.style.transform = "scale(1.1)"; }; btn.onmouseleave = function () { btn.style.transform = "scale(1)"; };
const chat = document.createElement("div"); chat.id = "anna-chat"; Object.assign(chat.style, { position: "fixed", bottom: `calc(${bottomOffset} + ${BTN_SIZE}px + 10px)`, right: sideOffset, width: `min(${CHAT_W}px, calc(100vw - 32px))`, height: CHAT_H + "px", maxHeight: `min(${CHAT_H}px, calc(100vh - 116px))`, borderRadius: "12px", overflow: "hidden", boxShadow: "0 8px 30px rgba(0,0,0,0.3)", display: "none", zIndex: "9998", }); chat.innerHTML = '<iframe src="' + ANNA_URL + '" style="width:100%;height:100%;border:none" allow="camera *; microphone *; geolocation *"></iframe>';
btn.addEventListener("click", function () { var open = chat.style.display === "none"; chat.style.display = open ? "block" : "none"; });
document.body.appendChild(chat); document.body.appendChild(btn); })(); </script> </body></html>Generate the chat URL on the backend
Section titled “Generate the chat URL on the backend”The examples below call REST/Credentials/GetToken, then call REST/AnnACorporate/User/UserHash with getAnnAChatLink set to true. They parse accessData.AnnAChatURL and return it from a backend method.
To use the forced flow, replace REST/AnnACorporate/User/UserHash with REST/AnnACorporate/User/UserHashForced and add userName to the request body.
using System.Net.Http;using System.Net.Http.Headers;using Newtonsoft.Json.Linq;
public async Task<string> GenerateCorporateChatUrlAsync( string userId, string userName = null){ using (var http = new HttpClient { BaseAddress = new Uri("https://YOUR_ANNA_ENVIRONMENT/") }) { var tokenResult = await http.PostAsJsonAsync("REST/Credentials/GetToken", new { hash = "YOUR_COMPANY_HASH", user = "YOUR_API_USER", pass = "YOUR_API_PASSWORD" }); tokenResult.EnsureSuccessStatusCode(); var tokenResponse = JObject.Parse(await tokenResult.Content.ReadAsStringAsync()); http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue( "Bearer", (string)tokenResponse["data"]["Token"]);
var endpoint = string.IsNullOrEmpty(userName) ? "REST/AnnACorporate/User/UserHash" : "REST/AnnACorporate/User/UserHashForced"; var request = new JObject { ["getAnnAChatLink"] = true, ["hash"] = "YOUR_COMPANY_HASH", ["userId"] = userId, ["startService"] = "" }; if (!string.IsNullOrEmpty(userName)) { request["userName"] = userName; }
var chatResult = await http.PostAsJsonAsync(endpoint, request); chatResult.EnsureSuccessStatusCode(); var response = JObject.Parse(await chatResult.Content.ReadAsStringAsync()); if ((bool?)response["success"] != true) { throw new InvalidOperationException((string)response["message"]); }
return (string)response["accessData"]["AnnAChatURL"]; }}using System.Net.Http.Headers;using System.Net.Http.Json;using System.Text.Json.Serialization;
public async Task<string> GenerateCorporateChatUrlAsync(string userId){ using var http = new HttpClient { BaseAddress = new Uri("https://YOUR_ANNA_ENVIRONMENT/") };
var tokenResult = await http.PostAsJsonAsync("REST/Credentials/GetToken", new { hash = "YOUR_COMPANY_HASH", user = "YOUR_API_USER", pass = "YOUR_API_PASSWORD" }); tokenResult.EnsureSuccessStatusCode(); var token = await tokenResult.Content.ReadFromJsonAsync<TokenResponse>(); http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue( "Bearer", token!.Data.Token);
var chatResult = await http.PostAsJsonAsync( "REST/AnnACorporate/User/UserHash", new { getAnnAChatLink = true, hash = "YOUR_COMPANY_HASH", userId, startService = "" }); chatResult.EnsureSuccessStatusCode(); var response = await chatResult.Content.ReadFromJsonAsync<ChatResponse>(); if (response?.Success != true) { throw new InvalidOperationException(response?.Message); }
return response.AccessData.AnnAChatURL;}
public sealed class TokenResponse{ public TokenData Data { get; set; } = new();}
public sealed class TokenData{ public string Token { get; set; } = string.Empty;}
public sealed class ChatResponse{ public bool Success { get; set; } public string Message { get; set; } = string.Empty; public ChatAccessData AccessData { get; set; } = new();}
public sealed class ChatAccessData{ [JsonPropertyName("AnnAChatURL")] public string AnnAChatURL { get; set; } = string.Empty;}For the forced flow, use REST/AnnACorporate/User/UserHashForced and add userName to the request object.
import java.net.URI;import java.net.http.HttpClient;import java.net.http.HttpRequest;import java.net.http.HttpResponse;import com.fasterxml.jackson.databind.JsonNode;import com.fasterxml.jackson.databind.ObjectMapper;
static String generateCorporateChatUrl(String userId) throws Exception { var client = HttpClient.newHttpClient(); var mapper = new ObjectMapper(); var baseUrl = "https://YOUR_ANNA_ENVIRONMENT/";
var tokenRequest = HttpRequest.newBuilder() .uri(URI.create(baseUrl + "REST/Credentials/GetToken")) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(""" { "hash": "YOUR_COMPANY_HASH", "user": "YOUR_API_USER", "pass": "YOUR_API_PASSWORD" } """)) .build(); var tokenResult = client.send( tokenRequest, HttpResponse.BodyHandlers.ofString()); tokenResult.body(); JsonNode token = mapper.readTree(tokenResult.body()).get("data").get("Token");
var chatBody = """ { "getAnnAChatLink": true, "hash": "YOUR_COMPANY_HASH", "userId": "%s", "startService": "" } """.formatted(userId); var chatRequest = HttpRequest.newBuilder() .uri(URI.create(baseUrl + "REST/AnnACorporate/User/UserHash")) .header("Content-Type", "application/json") .header("Authorization", "Bearer " + token.asText()) .POST(HttpRequest.BodyPublishers.ofString(chatBody)) .build(); var chatResult = client.send( chatRequest, HttpResponse.BodyHandlers.ofString()); if (chatResult.statusCode() / 100 != 2) { throw new IllegalStateException(chatResult.body()); }
JsonNode response = mapper.readTree(chatResult.body()); if (!response.get("success").asBoolean()) { throw new IllegalStateException(response.get("message").asText()); } return response.get("accessData").get("AnnAChatURL").asText();}For the forced flow, use the UserHashForced endpoint and add a userName property to chatBody.
<?php
function postJson(string $url, array $payload, ?string $token = null): array{ $headers = ["Content-Type: application/json"]; if ($token !== null) { $headers[] = "Authorization: Bearer " . $token; }
$curl = curl_init($url); curl_setopt_array($curl, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => $headers, CURLOPT_POSTFIELDS => json_encode($payload, JSON_THROW_ON_ERROR), ]); $body = curl_exec($curl); if ($body === false) { throw new RuntimeException(curl_error($curl)); } $statusCode = curl_getinfo($curl, CURLINFO_RESPONSE_CODE); curl_close($curl); if ($statusCode < 200 || $statusCode >= 300) { throw new RuntimeException("Request failed: " . $body); } return json_decode($body, true, 512, JSON_THROW_ON_ERROR);}
$baseUrl = "https://YOUR_ANNA_ENVIRONMENT/";$tokenResponse = postJson($baseUrl . "REST/Credentials/GetToken", [ "hash" => "YOUR_COMPANY_HASH", "user" => "YOUR_API_USER", "pass" => "YOUR_API_PASSWORD",]);
$chatResponse = postJson( $baseUrl . "REST/AnnACorporate/User/UserHash", [ "getAnnAChatLink" => true, "hash" => "YOUR_COMPANY_HASH", "userId" => "USER_ID", "startService" => "", ], $tokenResponse["data"]["Token"]);
if (($chatResponse["success"] ?? false) !== true) { throw new RuntimeException($chatResponse["message"] ?? "AnnA request failed");}echo $chatResponse["accessData"]["AnnAChatURL"] . PHP_EOL;For the forced flow, use UserHashForced and add "userName" => "USER_NAME" to the request body. This example requires the PHP cURL extension.
// PostJson and FromJson are backend helpers for JSON HTTP requests.&tokenBody = !'{"hash":"YOUR_COMPANY_HASH","user":"YOUR_API_USER","pass":"YOUR_API_PASSWORD"}'&tokenResponse = PostJson(!"YOUR_ANNA_ENVIRONMENT/REST/Credentials/GetToken", &tokenBody, !"")&tokenResponse.FromJson(&tokenData)
&chatBody = !'{"getAnnAChatLink":true,"hash":"YOUR_COMPANY_HASH","userId":"USER_ID","startService":""}'&chatResponse = PostJson( !"YOUR_ANNA_ENVIRONMENT/REST/AnnACorporate/User/UserHash", &chatBody, !"Bearer " + &tokenData.Data.Token)&chatResponse.FromJson(&chatData)
If &chatData.Success &annaChatUrl = &chatData.AccessData.AnnAChatURLElse &errorMessage = &chatData.MessageEndIfFor the forced flow, use UserHashForced and include userName in &chatBody. Adapt the HTTP and JSON helper names to the GeneXus version used by your application.
import requests
base_url = "https://YOUR_ANNA_ENVIRONMENT/"token_response = requests.post( f"{base_url}REST/Credentials/GetToken", json={ "hash": "YOUR_COMPANY_HASH", "user": "YOUR_API_USER", "pass": "YOUR_API_PASSWORD", }, timeout=30,)token_response.raise_for_status()token = token_response.json()["data"]["Token"]
chat_response = requests.post( f"{base_url}REST/AnnACorporate/User/UserHash", headers={"Authorization": f"Bearer {token}"}, json={ "getAnnAChatLink": True, "hash": "YOUR_COMPANY_HASH", "userId": "USER_ID", "startService": "", }, timeout=30,)chat_response.raise_for_status()response = chat_response.json()if response.get("success") is not True: raise RuntimeError(response.get("message", "AnnA request failed"))
anna_chat_url = response["accessData"]["AnnAChatURL"]print(anna_chat_url)For the forced flow, use UserHashForced and add "userName": "USER_NAME" to the request JSON. Install the Requests library before running the example.
Use the value returned in accessData.AnnAChatURL as ANNACHAT_URL_FROM_BACKEND in the client-side example. Do not return the company hash, API credentials, or bearer token to the browser.