Skip to main content
This guide combines the public-endpoint, asset, streaming and billing building blocks into one feature: acme/feedback-widget, a widget a customer embeds on their own site, where visitors ask questions and get AI-generated answers. It’s the most security-sensitive kind of plugin you can build, so read Public pages and endpoints first.

Architecture

1. Publish the loader

composer.json
The loader ends up at /acme-feedback.js, and the app bundle under /e/acme/feedback-widget/. The customer embeds one tag:
The loader creates an iframe pointing at your embed route, and communicates with it over postMessage:
widget/src/loader.js

2. Serve the embed page

src/RequestHandlers/EmbedView.php
frame-ancestors is the control that actually stops the widget being framed elsewhere. Default to 'none' when no domains are configured, rather than allowing everything. The template is a standalone page, not the app layout: <!DOCTYPE html>, your bundle, and a JSON config block.

3. Authenticate visitors

A visitor isn’t an Aikeedo user. Issue a short-lived token, signed with the widget’s own secret:
src/RequestHandlers/Api/SessionRequestHandler.php
If you support identity verification, where the customer’s site tells you who the visitor is, require the claim to be signed with the widget secret on the customer’s server. Accepting an unsigned identifier lets anyone read another visitor’s conversation.
Your middleware then authenticates later requests:
src/Middlewares/VisitorMiddleware.php

4. Answer with AI, billed to the owner

The visitor pays nothing; the workspace that owns the widget does.
Stream the answer back as server-sent events, and consume the real cost when the stream finishes:
See Streaming responses and AI models and credits for the details.

5. Verify ownership everywhere

Every route below /feedback/{wid}/ takes an ID from an untrusted caller. Check the whole chain:
Checking only the parent, or only the token, is the single most common way a widget leaks one customer’s conversations to another.

6. Development

Point an environment variable at your bundler while you work:
embed.twig
Test the embed on a separate local origin, not inside the Aikeedo tab, so you exercise the real cross-origin path.

Launch checklist

frame-ancestors is set, and defaults to 'none' when nothing is configured.
Tokens are signed per widget, short-lived, and never minted from caller-supplied IDs.
Every nested ID is checked against both the visitor and the widget.
Credits are billed to the widget’s workspace, and checked before generation starts.
Requests are rate-limited per visitor and per widget, and payload size is capped.
Responses contain nothing internal: no workspace details, no configuration, no other visitors.
Disabling the feature or deactivating the widget returns 404 for every route.
The loader is small, framework-free, and degrades quietly if the iframe fails to load.

Official chatbots

If you need embeddable AI chatbots rather than a custom widget, the official Chatbots plugin is available on the Aikeedo Marketplace: