Put your agent in your site
The app you build in the Studio (the agent and its own interface) can live inside any site: WordPress, Shopify, Wix or your own.
1. Publish it
In the Studio: your agent → Project → Deployment → Publish. The app gets its
address, for example https://my-shop-8c84.digitorn.app. Project → Embed
then gives you every code below, already filled in.
2. Show it in your page
<iframe
src="https://my-shop-8c84.digitorn.app"
style="width:100%;height:640px;border:0;border-radius:12px"
allow="clipboard-write; microphone">
</iframe>
That's enough for visitors: each browser is a guest, who finds their conversations again on the same browser. Publish again after a change and every page that embeds the app shows the new version.
3. Recognize your users
When your user is signed in to your site, open the app already signed in as them, with your own id for them. Their conversations, their plan and the accounts they connected (their Gmail, their calendar…) then follow them on every device: same id, same person.
Your user's browser Your server Digitorn
1. opens /assistant ────────▶ 2. reads YOUR session: user_482
3. POST /v1/users/user_482/launch ─────▶ 4. checks sk_, finds or
(sk_ from its environment) creates user_482, issues a
ticket (once, 2 minutes)
5. gets { url } ◀───────────────────────
6. gets the page with ◀─────── puts url in the iframe src
<iframe src="url">
7. the iframe opens ──────────────────────────────────────────────────▶ 8. trades the ticket for a
session as user_482
Four rules:
- The id comes from your own session, never from the URL or anything the
browser sends. Otherwise anyone could open anyone's account
(
/assistant?user=someone-else). - The secret key stays on your server, in its environment.
- A new link for every page view: each one works once.
- A stable id: the same user keeps the same id for life (their id in your database, not a session id or an e-mail that may change).
Your server asks for a link each time it shows the page:
// Node / Express: the user signed in to YOUR site
app.get("/assistant", requireLogin, async (req, res) => {
const user = req.session.user; // from your session, never from the URL
const r = await fetch(`https://api.digitorn.ai/v1/users/${encodeURIComponent(user.id)}/launch`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.DIGITORN_SECRET_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ name: user.name, email: user.email }),
});
if (!r.ok) return res.status(502).send("Assistant unavailable");
const { url } = await r.json(); // works once, for 2 minutes
res.render("assistant", { assistantUrl: url }); // <iframe src="<%= assistantUrl %>">
});
<?php // PHP: the user signed in to YOUR site
$user = $_SESSION['user']; // from your session, never from the URL
$ch = curl_init('https://api.digitorn.ai/v1/users/' . rawurlencode($user['id']) . '/launch');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('DIGITORN_SECRET_KEY'), 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode(['name' => $user['name'], 'email' => $user['email']]),
]);
$url = json_decode(curl_exec($ch), true)['url'];
?>
<iframe src="<?= htmlspecialchars($url) ?>" style="width:100%;height:640px;border:0"></iframe>
{
"url": "https://my-shop-8c84.digitorn.app/#digitorn_signin=Qd…",
"fragment": "#digitorn_signin=Qd…",
"expires_at": "2026-10-11T10:42:00Z",
"user": { "id": "user_482", "name": "Aïcha Koné", "created": true }
}
- The link works once, within 2 minutes: ask for a new one each time you render the page. The app then keeps the user signed in.
- The ticket travels after
#: browsers never send that part to any server, so it never appears in a log or aReferer. nameandemailare optional and update the user's profile.- To open the app at another of your allowed sites (a custom domain), pass
"app_url": "https://chat.myshop.com/". Only the project's allowed sites (Project → Access) are accepted. - A user who signed up by themselves in the app, or a disabled user, can never be
opened this way (
user_conflict,user_disabled). urlis in the answer once the app is published (or when you passapp_url); before that you only getfragment.- Errors:
401invalid or revoked key,403 server_only(called from a browser) oruser_disabled,409 user_conflict,422invalid id orapp_url_not_allowed,429too many requests.
4. Tell the agent more
Your page can tell the agent what it should know about the moment: the page, the cart, an order. Send it again whenever it changes.
<script>
const agent = document.querySelector("iframe");
const send = () => agent.contentWindow.postMessage({
type: "digitorn:context",
page: { title: document.title, url: location.href },
data: { cart: "2 bags of rice 25 kg", total: "36 000 FCFA" }
}, "https://my-shop-8c84.digitorn.app");
// The app says "digitorn:ready" once it listens; send then (and on load).
window.addEventListener("message", (e) => {
if (e.source === agent.contentWindow && e.data?.type === "digitorn:ready") send();
});
agent.addEventListener("load", send);
</script>
Call send() again whenever the page or the cart changes.
The agent sees it as declared by your site, not as a verified identity: it
greets "Aïcha" and talks about her cart. Anyone can change what a page sends
(browser tools), so never put a right in it (a tier, a discount, a role).
Facts that matter go through your server: the link of step 3 for who she is,
and claims for what you vouch for. When both say something,
the agent trusts the server.
Without an iframe
Building your own interface? Talk to the agent from your server with the
conversations API, or use the @digitornai/sdk React
package in your web app.