Agent

Embed in a web page

Issue embed tokens for signed-in users, mount PraxisKit, and verify a real conversation.

Add PraxisKit to your page so signed-in users can talk to a service agent. This local order-assistant example covers backend issuance, mounting, and user isolation. For other interfaces, see Choose an integration.

You need Bun, an available service agent and runtime environment, and an API key in the same organization. The example connects to Develop. Store PRAXIS_API_KEY only in the backend environment. The browser authenticates with your host; your host verifies identity, requests a token from Praxis, and passes that token to Kit.

1. Prepare your agent

In the Develop Dashboard, open 服务智能体 (Service agents) and select an existing agent. Save its instructions under 接入与配置 → 配置 → 指令. Under 配置 → 安全, select a runtime environment, enable 允许 localhost, and click 保存安全设置. The example runs at http://localhost:4242. Use a dedicated test agent with an empty origin allowlist and localhost enabled. Local requests are rejected when the token inherits a nonempty HTTPS origin list. Add your website's origin when moving to HTTPS hosting.

Embed policies can currently only be configured in the Dashboard. Copy the public agent ID (agent_…). After the saved confirmation appears, continue with actual issuance; saving settings alone does not prove that chat works.

2. Create a token endpoint

Create a directory and save this as server.ts. This loopback-only demo uses HTTP Basic authentication for two test accounts. Set your own test password in DEMO_PASSWORD; it is unrelated to your Praxis API key. The server derives the user ID from the authenticated account and ignores any client-supplied user ID.

import { timingSafeEqual } from "node:crypto";

const apiKey = process.env.PRAXIS_API_KEY;
const agentId = process.env.PRAXIS_AGENT_ID;
const password = process.env.DEMO_PASSWORD;
if (!apiKey || !agentId || !password) throw new Error("Set the three server variables");
const origin = "http://localhost:4242";
const noStore = { "Cache-Control": "no-store" };
function authenticate(request: Request): string | undefined {
  const supplied = Buffer.from(request.headers.get("Authorization") ?? "");
  for (const id of ["customer_123", "customer_456"]) {
    const expected = Buffer.from("Basic " + Buffer.from(`${id}:${password}`).toString("base64"));
    if (supplied.length === expected.length && timingSafeEqual(supplied, expected)) return id;
  }
}
Bun.serve({
  hostname: "127.0.0.1", port: 4242,
  async fetch(request) {
    const url = new URL(request.url);
    if (url.origin !== origin) return new Response("Use localhost:4242", { status: 403 });
    const userId = authenticate(request);
    if (!userId) return new Response("Sign in", {
      status: 401, headers: { ...noStore, "WWW-Authenticate": 'Basic realm="Local demo"' },
    });
    if (request.method === "GET" && url.pathname === "/") {
      return new Response(Bun.file("index.html"), { headers: noStore });
    }
    if (request.method !== "POST" || url.pathname !== "/api/praxis-token") {
      return new Response("Not found", { status: 404 });
    }
    if (request.headers.get("Origin") !== origin) {
      return new Response("Origin rejected", { status: 403 });
    }
    try {
      const response = await fetch("https://api-develop.deepraxis.com/agent/v1/embed_tokens", {
        method: "POST",
        headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
        body: JSON.stringify({ agent: agentId, external_user_id: userId }),
        signal: AbortSignal.timeout(30_000),
      });
      if (!response.ok) return new Response("Token issuance failed", {
        status: response.status, headers: noStore,
      });
      const data = await response.json();
      if (typeof data.token !== "string" || typeof data.expires_at !== "string") {
        throw new Error("Invalid token response");
      }
      return Response.json({ token: data.token, expires_at: data.expires_at }, { headers: noStore });
    } catch {
      return new Response("Token service unavailable", { status: 502, headers: noStore });
    }
  },
});
console.log(origin);

Set the three variables in the server process. Replace PRAXIS_AGENT_ID with the actual ID. Save index.html in the next step before starting.

export PRAXIS_AGENT_ID="agent_REPLACE_ME"
# Set PRAXIS_API_KEY and DEMO_PASSWORD in this server process, then run:
bun server.ts

Expected: the terminal prints the local URL. The host rejects unauthenticated requests. Only authenticated, same-origin requests reach Praxis. Praxis returns HTTP 201 on issuance; the host returns only token and expires_at, with caching disabled.

For an existing application, replace authenticate with its session validation. Mount the route behind login and CSRF protection, and recheck identity on every issuance. Do not use these test accounts as your production login system. See Embed tokens and users for expiration and refresh.

3. Mount the component

Choose one frontend implementation. The API address, CDN, and agent must use the same environment. These examples pin Kit 0.2.8 on Develop.

Save this as index.html in the same directory, then run the startup command above.

<!doctype html>
<html lang="en">
<meta charset="utf-8">
<title>Order assistant</title>
<script type="module" src="https://kit-develop.praxisusercontent.com/v0.2.8/praxis-kit.js" integrity="sha384-vey+VZZcFIWJq9Fmv/nmLxspKtXRiL8XiDOfg2g2QFvwZi5MA+tkiQvPBtoMS8eU" crossorigin="anonymous"></script>
<praxis-agent style="display:block;height:600px;width:100%"></praxis-agent>
<p id="status" role="status"></p>
<script type="module">
  await customElements.whenDefined("praxis-agent");
  const agent = document.querySelector("praxis-agent");
  agent.addEventListener("praxis.error", () => {
    document.querySelector("#status").textContent = "Chat unavailable. Check your sign-in and configuration.";
  });
  agent.setOptions({
    api: {
      url: "https://api-develop.deepraxis.com",
      getToken: async () => {
        const response = await fetch("/api/praxis-token", {
          method: "POST", credentials: "same-origin", cache: "no-store",
        });
        if (!response.ok) throw new Error(`Token request failed: ${response.status}`);
        const data = await response.json();
        if (typeof data.token !== "string" || !data.token) throw new Error("Missing token");
        return data.token;
      },
    },
    agent: { name: "Order assistant" },
  });
</script>
</html>

Expected: a chat area appears with a height of 600 pixels. api.url sets the API address, getToken returns a token string, and agent.name sets the display name. Wait for registration before calling setOptions. The praxis.ready event only confirms mounting. A real reply is required to verify the integration.

4. Verify the integration

  1. Open http://localhost:4242. Sign in as customer_123 using your test password. Ask what order questions the assistant can help with and confirm a reply arrives.
  2. In the network panel, check that the host token request succeeds without returning the API key. Session requests should use an embed token. Do not copy credentials into logs or screenshots.
  3. In a separate browser profile or private window, sign in as customer_456. Expect no access to the first account's conversations. Two tabs sharing one login do not test isolation.
  4. Request the local token endpoint without credentials. Expect HTTP 401 from the host. In your application, also verify that signing out prevents further issuance.
curl --silent --output /dev/null --write-out "%{http_code}\n" \
  -X POST http://localhost:4242/api/praxis-token \
  -H "Origin: http://localhost:4242"

Troubleshoot failures

These are defined API failure cases. Check the actual response in your network panel:

  • Issuance HTTP 400: confirm that the embed policy is saved and the runtime environment is available.
  • Issuance HTTP 404: check the agent ID and confirm the API key and service agent belong to the same organization.
  • Chat returns EMBED_ORIGIN_NOT_ALLOWED (HTTP 403): check the page origin. Enable localhost for local testing or save the HTTPS origin for your hosted site.

Next steps

Export

On this page