Show customer details from your own system (Stripe subscription, internal CRM, user admin, etc.) right next to the conversation in your Boei inbox. When an agent opens a thread, Boei loads a page from your server as a sidebar iframe. You render whatever HTML you want — plan info, MRR, recent invoices, action buttons — and it appears next to the messages.
How to get there: Go to Settings → Websites in the top menu → select your website → Integrations tab → Customer Panel.
Good fits:
If you want the AI chatbot (not a human agent) to use this data in its replies, use an AI Action instead — the Customer Panel is for the human in the Inbox.
email — the customer's emailthread — the Boei thread ID (optional, useful if you want to log something)token — the shared token (your server checks this matches)theme — light or darkNo data flows back to Boei — your page just renders. The iframe is sandboxed, so it can't read anything from the rest of the Boei interface.
Create an endpoint on your server (e.g. https://yourapp.com/boei-customer-panel). It should:
token, email, and theme query paramsPHP:
if (($_GET['token'] ?? '') !== getenv('BOEI_PANEL_TOKEN')) {
http_response_code(403);
exit;
}
Node / Express:
if (req.query.token !== process.env.BOEI_PANEL_TOKEN) {
return res.status(403).end();
}
Laravel:
if ($request->query('token') !== config('services.boei.panel_token')) {
abort(403);
}
If you need to rotate the token (e.g. you leaked it in a screenshot), click Regenerate in the settings and update the value on your server.
Include the stylesheet and set the theme on <body>:
<!doctype html>
<html><head>
<meta charset="utf-8">
<link rel="stylesheet" href="https://app.boei.help/customer-panel.css">
</head>
<body class="boei-panel" data-theme="light">
<!-- your content here -->
</body></html>
Set data-theme="dark" when theme=dark so the panel matches the agent's theme (once dark mode ships to the inbox).
Group related rows inside a .boei-section:
<section class="boei-section">
<div class="boei-section-title">Subscription</div>
<div class="boei-section-body">
<div class="boei-row">
<span class="boei-row-label">Plan</span>
<span class="boei-row-value">Pro</span>
</div>
<div class="boei-row">
<span class="boei-row-label">MRR</span>
<span class="boei-row-value">$49</span>
</div>
</div>
</section>
Use <details> + <summary> — no JavaScript needed, the disclosure arrow is styled for you:
<details class="boei-section">
<summary class="boei-section-title">Recent invoices</summary>
<div class="boei-section-body">
<div class="boei-row">
<span class="boei-row-label">Apr 01</span>
<span class="boei-row-value">$49 paid</span>
</div>
<div class="boei-row">
<span class="boei-row-label">Mar 01</span>
<span class="boei-row-value">$49 paid</span>
</div>
</div>
</details>
Add the open attribute to start expanded: <details class="boei-section" open>.
The .boei-icon class sets 14×14px sizing so any SVG or icon-font glyph lines up with the text. You can also drop an emoji in for a quick preview:
<div class="boei-row">
<span class="boei-row-label">
<span class="boei-icon">📈</span> MRR
</span>
<span class="boei-row-value">$49</span>
</div>
With an inline SVG:
<span class="boei-icon">
<svg viewBox="0 0 16 16" fill="currentColor"><!-- your SVG paths --></svg>
</span>
The .boei-icon class uses color: var(--boei-fg-muted) by default — set color yourself if you want a coloured icon.
Status pills in four variants:
<span class="boei-badge">Neutral</span>
<span class="boei-badge boei-badge-success">Active</span>
<span class="boei-badge boei-badge-warning">Past due</span>
<span class="boei-badge boei-badge-danger">Cancelled</span>
<span class="boei-badge boei-badge-info">Trial</span>
Common pattern — status next to a value:
<div class="boei-row">
<span class="boei-row-label">Plan</span>
<span class="boei-row-value">
Pro <span class="boei-badge boei-badge-success">Active</span>
</span>
</div>
Open links in a new tab so they don't try to load inside the narrow iframe:
<div class="boei-row">
<span class="boei-row-label">Profile</span>
<a class="boei-row-value" href="https://app.example.com/users/123"
target="_blank" rel="noopener">View in admin ↗</a>
</div>
Buttons for common workflows (open in Stripe, start a refund, email the customer):
<div style="display:flex; gap:8px; margin-top:10px;">
<a class="boei-button" href="https://dashboard.stripe.com/customers/cus_xxx"
target="_blank" rel="noopener">Open in Stripe</a>
<a class="boei-button" href="mailto:jane@example.com"
target="_blank" rel="noopener">Email customer</a>
</div>
When no customer is found for the email:
<div class="boei-empty">
No customer found for jane@example.com
</div>
The iframe doesn't know how tall your content is. Add this small script at the bottom of <body> so it auto-fits:
<script>
const post = () => parent.postMessage(
{ type: 'boei-customer-panel:resize', height: document.documentElement.scrollHeight },
'*'
);
window.addEventListener('load', post);
new ResizeObserver(post).observe(document.documentElement);
</script>
This posts the content height whenever it changes (e.g. a section is collapsed/expanded) and Boei resizes the iframe accordingly. Height is clamped between 100px and 4000px.
Putting it all together — a full PHP page showing Stripe-style subscription info:
<?php
if (($_GET['token'] ?? '') !== getenv('BOEI_PANEL_TOKEN')) {
http_response_code(403);
exit;
}
$email = $_GET['email'] ?? '';
$theme = ($_GET['theme'] ?? 'light') === 'dark' ? 'dark' : 'light';
// Look up your customer however you normally would
$customer = findCustomerByEmail($email);
?>
<!doctype html>
<html><head>
<meta charset="utf-8">
<link rel="stylesheet" href="https://app.boei.help/customer-panel.css">
</head>
<body class="boei-panel" data-theme="<?= htmlspecialchars($theme) ?>">
<?php if (!$customer): ?>
<div class="boei-empty">
No account found for <?= htmlspecialchars($email) ?>
</div>
<?php else: ?>
<section class="boei-section">
<div class="boei-section-title">Subscription</div>
<div class="boei-section-body">
<div class="boei-row">
<span class="boei-row-label">Plan</span>
<span class="boei-row-value">
<?= htmlspecialchars($customer->plan) ?>
<span class="boei-badge boei-badge-<?= $customer->active ? 'success' : 'danger' ?>">
<?= $customer->active ? 'Active' : 'Cancelled' ?>
</span>
</span>
</div>
<div class="boei-row">
<span class="boei-row-label">MRR</span>
<span class="boei-row-value">$<?= number_format($customer->mrr) ?></span>
</div>
<div class="boei-row">
<span class="boei-row-label">Signed up</span>
<span class="boei-row-value"><?= $customer->created_at->format('M j, Y') ?></span>
</div>
</div>
</section>
<details class="boei-section">
<summary class="boei-section-title">Recent invoices</summary>
<div class="boei-section-body">
<?php foreach ($customer->recentInvoices as $invoice): ?>
<div class="boei-row">
<span class="boei-row-label"><?= $invoice->date->format('M j') ?></span>
<span class="boei-row-value">$<?= $invoice->amount ?> <?= $invoice->status ?></span>
</div>
<?php endforeach; ?>
</div>
</details>
<div style="display:flex; gap:8px; margin-top:10px;">
<a class="boei-button"
href="https://dashboard.stripe.com/customers/<?= $customer->stripe_id ?>"
target="_blank" rel="noopener">Open in Stripe</a>
<a class="boei-button"
href="https://app.example.com/admin/users/<?= $customer->id ?>"
target="_blank" rel="noopener">Open in admin</a>
</div>
<?php endif; ?>
<script>
const post = () => parent.postMessage(
{ type: 'boei-customer-panel:resize', height: document.documentElement.scrollHeight },
'*'
);
window.addEventListener('load', post);
new ResizeObserver(post).observe(document.documentElement);
</script>
</body></html>
Don't want to hand-write the HTML? Paste the prompt below into Claude Code, Cursor, ChatGPT, or any AI coding assistant. It includes the endpoint spec, the CSS classes to use, and placeholders for your token — so the AI can generate a working page directly against your own customer data.
Replace YOUR_TOKEN_HERE with the token from your Boei settings before pasting.
Build a "Boei Customer Panel" page that renders inside an iframe in the Boei inbox.
## Endpoint
Create an HTTP endpoint on my server that Boei can load. Boei calls it with these query params:
- `email` — the customer's email (use this to look them up)
- `thread` — Boei's thread id (optional, useful for logging)
- `token` — shared secret, must equal: YOUR_TOKEN_HERE
- `theme` — "light" or "dark" (mirror onto <body data-theme="...">)
If `token` does not match, return HTTP 403 and stop.
## Data source
Ask me where the customer data lives (Stripe, internal DB, CRM, etc.) and what fields matter (plan, MRR, signup date, recent orders, etc.). If I don't specify, assume Stripe customers by email + show plan, status, MRR, and recent invoices.
## Output
Return an HTML page that:
1. Loads the Boei stylesheet: `<link rel="stylesheet" href="https://app.boei.help/customer-panel.css">`
2. Uses `<body class="boei-panel" data-theme="...">` (mirror the theme param).
3. Groups info in `<section class="boei-section">` blocks with `.boei-section-title` + `.boei-section-body`.
4. Renders key/value pairs with `<div class="boei-row"><span class="boei-row-label">...</span><span class="boei-row-value">...</span></div>`.
5. Uses `<details class="boei-section">` + `<summary class="boei-section-title">` for collapsible groups.
6. Uses status pills via `.boei-badge` + `.boei-badge-success|warning|danger|info`.
7. Uses `.boei-button` for action links (Open in Stripe, Open in admin, etc.). All external links must have `target="_blank" rel="noopener"` because the iframe is sandboxed.
8. Shows `<div class="boei-empty">No customer found for {email}</div>` when the email has no match.
9. Escapes all user-controlled output.
10. Ends with this auto-resize snippet so the iframe sizes to content:html
## Security
- Store the token in an env var, not in source (but hardcode it for local dev is fine).
- Reject requests where the token doesn't match.
- Do not render scripts from customer data.
Full CSS class reference: https://app.boei.help/customer-panel.css
Full docs: https://feedback.boei.help/f/knowledge-base/customer-panel
Use whatever stack I already have in this project. If unclear, ask.`
Tip: for the fastest path, copy the prompt straight from Website Settings → Integrations → Customer Panel — the Copy AI prompt button already fills in your token.
The sidebar doesn't appear at all.
Open a conversation where you know the customer's email. The sidebar only shows when the thread has an email identifier (SMS-only threads without an email won't trigger it).
"Refused to display in a frame" error.
Your server is sending an X-Frame-Options: DENY or strict Content-Security-Policy: frame-ancestors header. Either remove those headers on this specific URL, or add https://app.boei.help to the allowed frame-ancestors.
Clicking a link reloads Boei instead of opening a new tab.
Add target="_blank" rel="noopener" to your <a> tags. The iframe is sandboxed so regular navigation replaces the whole panel.
Height is wrong / content is cut off.
Make sure you've included the auto-resize script at the bottom of <body>. Test in isolation by loading your URL in a regular browser tab — the window.parent.postMessage call will no-op outside an iframe, which is fine.
Styling looks odd.
Check the stylesheet is loading (open the URL directly and inspect). The .boei-panel class on <body> is what applies the base typography and spacing.
allow-scripts allow-forms allow-popups), so your page can run JavaScript and open external links but can't read Boei's cookies or storage.