How to embed AnnA Chat with a Corporate User V2
This guide shows how to embed AnnA Chat for an authenticated Corporate User using the V2 API. The browser receives only the generated AnnA Chat URL. Keep the company hash, encryption and decryption keys, user data, and all V2 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 Corporate User encryption and decryption keys.
- The Corporate User ID.
- The base URL of your AnnA environment.
The V2 integration uses 3DES encryption with CBC mode. See Encryption for the encryption requirements and helper methods used by the examples.
Integration flow
Section titled “Integration flow”- Your backend sends an encrypted
GENERATE_USER_HASHrequest toaannacorporateuser.aspx. - Your backend sets
GET_ANNA_CHAT_LINKtoSso that AnnA returns the chat links. - Your backend decrypts the response and reads the
AnnAChatURLproperty. - Your client-side code uses that generated URL as the iframe source.
The generated URL can expire. Generate it on the server for the signed-in user and do not put the company hash or encryption keys in browser code.
Response from AnnA
Section titled “Response from AnnA”After decrypting the response, parse the JSON returned by AnnA. The AnnAChatURL property is the URL to use in the iframe.
{ "Status": "OK", "Message": "User Hash generated successfully", "Hash": "USER_HASH", "ChatURL": "CHAT_URL_WITH_USER_HASH", "EmbeddedURL": "EMBEDDED_URL_WITH_USER_HASH", "AnnAChatURL": "ANNACHAT_URL_FOR_IFRAME", "Expires": "EXPIRATION_DATETIME"}If the response status is not successful, 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 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”Send the request to YOUR_ANNA_URL/aannacorporateuser.aspx with ACTION=GENERATE_USER_HASH and GET_ANNA_CHAT_LINK=S. Encrypt the fields that the V2 API requires with the encryption key and request IV. Decrypt the returned payload with the response IV, then read AnnAChatURL from the JSON.
The Encrypt, Decrypt, GenerateIV, and DecryptAnnAResponse helpers in the examples represent the 3DES helpers described in Encryption. They must run on the backend.
using System.Collections.Specialized;using System.Net;using System.Text;using Newtonsoft.Json.Linq;
public string GenerateCorporateChatUrl(){ var companyHash = "YOUR_COMPANY_HASH"; var encryptionKey = "YOUR_CORPORATE_USER_ENCRYPTION_KEY"; var decryptionKey = "YOUR_CORPORATE_USER_DECRYPTION_KEY"; var userId = "USER_ID"; var startService = ""; var requestIv = GenerateIV();
var form = new NameValueCollection { ["HASH"] = companyHash, ["ANNAEXEC"] = requestIv, ["ACTION"] = Encrypt("GENERATE_USER_HASH", encryptionKey, requestIv), ["USER_ID"] = Encrypt(userId, encryptionKey, requestIv), ["START_SERVICE"] = Encrypt(startService, encryptionKey, requestIv), ["GET_ANNA_CHAT_LINK"] = "S" };
using (var client = new WebClient()) { client.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded"; var responseText = Encoding.UTF8.GetString(client.UploadValues( "https://YOUR_ANNA_URL/aannacorporateuser.aspx", "POST", form)); var jsonText = DecryptAnnAResponse( responseText, requestIv, encryptionKey, decryptionKey); var response = JObject.Parse(jsonText);
if ((string)response["Status"] != "OK") { throw new InvalidOperationException((string)response["Message"]); }
return (string)response["AnnAChatURL"]; }}using System.Net.Http;using System.Text.Json;
public async Task<string> GenerateCorporateChatUrlAsync(){ using var http = new HttpClient(); var requestIv = GenerateIV(); var form = new Dictionary<string, string> { ["HASH"] = "YOUR_COMPANY_HASH", ["ANNAEXEC"] = requestIv, ["ACTION"] = Encrypt("GENERATE_USER_HASH", "ENCRYPTION_KEY", requestIv), ["USER_ID"] = Encrypt("USER_ID", "ENCRYPTION_KEY", requestIv), ["START_SERVICE"] = Encrypt("", "ENCRYPTION_KEY", requestIv), ["GET_ANNA_CHAT_LINK"] = "S" };
using var result = await http.PostAsync( "https://YOUR_ANNA_URL/aannacorporateuser.aspx", new FormUrlEncodedContent(form)); result.EnsureSuccessStatusCode();
var responseText = await result.Content.ReadAsStringAsync(); var jsonText = DecryptAnnAResponse( responseText, requestIv, "ENCRYPTION_KEY", "DECRYPTION_KEY"); using var response = JsonDocument.Parse(jsonText); var root = response.RootElement;
if (root.GetProperty("Status").GetString() != "OK") { throw new InvalidOperationException(root.GetProperty("Message").GetString()); }
return root.GetProperty("AnnAChatURL").GetString()!;}import java.net.URI;import java.net.http.HttpClient;import java.net.http.HttpRequest;import java.net.http.HttpResponse;import java.net.URLEncoder;import java.nio.charset.StandardCharsets;import com.fasterxml.jackson.databind.JsonNode;import com.fasterxml.jackson.databind.ObjectMapper;
static String generateCorporateChatUrl() throws Exception { var requestIv = generateInitializationVector(); var form = "HASH=" + encode("YOUR_COMPANY_HASH") + "&ANNAEXEC=" + encode(requestIv) + "&ACTION=" + encode(encrypt3DES("GENERATE_USER_HASH", "ENCRYPTION_KEY", requestIv)) + "&USER_ID=" + encode(encrypt3DES("USER_ID", "ENCRYPTION_KEY", requestIv)) + "&START_SERVICE=" + encode(encrypt3DES("", "ENCRYPTION_KEY", requestIv)) + "&GET_ANNA_CHAT_LINK=S";
var request = HttpRequest.newBuilder() .uri(URI.create("https://YOUR_ANNA_URL/aannacorporateuser.aspx")) .header("Content-Type", "application/x-www-form-urlencoded") .POST(HttpRequest.BodyPublishers.ofString(form)) .build(); var result = HttpClient.newHttpClient().send( request, HttpResponse.BodyHandlers.ofString()); if (result.statusCode() / 100 != 2) { throw new IllegalStateException(result.body()); }
var jsonText = decryptAnnAResponse( result.body(), requestIv, "ENCRYPTION_KEY", "DECRYPTION_KEY"); JsonNode response = new ObjectMapper().readTree(jsonText); if (!"OK".equals(response.get("Status").asText())) { throw new IllegalStateException(response.get("Message").asText()); } return response.get("AnnAChatURL").asText();}
static String encode(String value) { return URLEncoder.encode(value, StandardCharsets.UTF_8);}This example uses Jackson for JSON parsing.
<?php
$requestIv = generateIV();$encryptionKey = "YOUR_CORPORATE_USER_ENCRYPTION_KEY";$decryptionKey = "YOUR_CORPORATE_USER_DECRYPTION_KEY";
$fields = [ "HASH" => "YOUR_COMPANY_HASH", "ANNAEXEC" => $requestIv, "ACTION" => encrypt3DES("GENERATE_USER_HASH", $encryptionKey, $requestIv), "USER_ID" => encrypt3DES("USER_ID", $encryptionKey, $requestIv), "START_SERVICE" => encrypt3DES("", $encryptionKey, $requestIv), "GET_ANNA_CHAT_LINK" => "S",];
$curl = curl_init("https://YOUR_ANNA_URL/aannacorporateuser.aspx");curl_setopt_array($curl, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_POSTFIELDS => http_build_query($fields),]);$responseText = curl_exec($curl);if ($responseText === false) { throw new RuntimeException(curl_error($curl));}curl_close($curl);
$jsonText = decryptAnnAResponse( $responseText, $requestIv, $encryptionKey, $decryptionKey);$response = json_decode($jsonText, true, 512, JSON_THROW_ON_ERROR);if (($response["Status"] ?? "") !== "OK") { throw new RuntimeException($response["Message"] ?? "AnnA request failed");}
echo $response["AnnAChatURL"] . PHP_EOL;This example requires the PHP cURL extension and the 3DES helper functions.
// GenerateIV, Encrypt, DecryptAnnAResponse, and FromJson are backend helpers.&requestIv = GenerateIV()&body = !"HASH=" + UrlEncode(!"YOUR_COMPANY_HASH")&body += !"&ANNAEXEC=" + UrlEncode(&requestIv)&body += !"&ACTION=" + UrlEncode(Encrypt(!"GENERATE_USER_HASH", !"ENCRYPTION_KEY", &requestIv))&body += !"&USER_ID=" + UrlEncode(Encrypt(!"USER_ID", !"ENCRYPTION_KEY", &requestIv))&body += !"&START_SERVICE=" + UrlEncode(Encrypt(!"", !"ENCRYPTION_KEY", &requestIv))&body += !"&GET_ANNA_CHAT_LINK=S"
&httpClient.Host = !"YOUR_ANNA_URL"&httpClient.AddHeader(!"Content-Type", !"application/x-www-form-urlencoded")&httpClient.Execute(!"POST", !"/aannacorporateuser.aspx", &body)
&jsonText = DecryptAnnAResponse(&httpClient.ToString(), &requestIv, !"ENCRYPTION_KEY", !"DECRYPTION_KEY")&response.FromJson(&jsonText)&annaChatUrl = &response.AnnAChatURLAdapt the HTTP client and response object names to the GeneXus version used by your application.
import requests
request_iv = generate_iv()encryption_key = "YOUR_CORPORATE_USER_ENCRYPTION_KEY"decryption_key = "YOUR_CORPORATE_USER_DECRYPTION_KEY"
form = { "HASH": "YOUR_COMPANY_HASH", "ANNAEXEC": request_iv, "ACTION": encrypt_3des("GENERATE_USER_HASH", encryption_key, request_iv), "USER_ID": encrypt_3des("USER_ID", encryption_key, request_iv), "START_SERVICE": encrypt_3des("", encryption_key, request_iv), "GET_ANNA_CHAT_LINK": "S",}
result = requests.post( "https://YOUR_ANNA_URL/aannacorporateuser.aspx", data=form, timeout=30,)result.raise_for_status()json_text = decrypt_anna_response( result.text, request_iv, encryption_key, decryption_key)response = result.json() if json_text == result.text else json.loads(json_text)
if response.get("Status") != "OK": raise RuntimeError(response.get("Message", "AnnA request failed"))
anna_chat_url = response["AnnAChatURL"]print(anna_chat_url)The generate_iv, encrypt_3des, and decrypt_anna_response helpers must use the V2 3DES contract. Install the Requests library before running the example.
Use the value returned in AnnAChatURL as ANNACHAT_URL_FROM_BACKEND in the client-side example. Do not return the company hash, encryption keys, or decrypted authentication hash to the browser.