Customer Panel

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.

When to use it

Good fits:

  • SaaS support — show plan, MRR, signup date, last login next to the conversation
  • E-commerce support — show recent orders, lifetime value, shipping addresses
  • Internal tools — link straight to the customer's record in your admin / Stripe / HubSpot

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.

How it works

  1. In Boei, you configure a URL that points to a page on your server.
  2. Boei auto-generates a shared token — a long random string your server uses to verify requests.
  3. When an agent opens a conversation in the inbox, Boei loads your URL inside a sandboxed iframe, adding these query parameters:
    • email — the customer's email
    • thread — the Boei thread ID (optional, useful if you want to log something)
    • token — the shared token (your server checks this matches)
    • themelight or dark
  4. Your page validates the token, looks up the customer, and renders HTML.
  5. The iframe auto-resizes to fit your content using a small JavaScript snippet.

No 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.

Setup

1. Build your page

Create an endpoint on your server (e.g. https://yourapp.com/boei-customer-panel). It should:

  • Read the token, email, and theme query params
  • Reject the request (403) if the token doesn't match the one from Boei settings
  • Look up the customer in your database
  • Return an HTML response using our drop-in stylesheet

2. Configure in Boei

  1. In Website Settings → Integrations → Customer Panel, paste your URL.
  2. A shared token is generated automatically. Copy it.
  3. Paste the token into your server code as a constant (or environment variable).
  4. Open any conversation in the inbox — the sidebar should appear.

3. Verify the token

PHP:

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.

HTML patterns

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).

Sections

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>

Collapsible sections

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>.

Icons

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.

Badges

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>

Action buttons

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>

Empty state

When no customer is found for the email:

<div class="boei-empty">
  No customer found for jane@example.com
</div>

Auto-resize

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.

Complete example

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>

Let AI build it for you

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
<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>

## 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.

Troubleshooting

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.

Security notes

  • The token is a shared secret — keep it in an environment variable or secret manager, not in source control.
  • The iframe is sandboxed (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.
  • Query params appear in server access logs. For higher-security data (PII, health records), add an extra layer on top of the token — e.g. require the request to come from a specific IP range, or check a signed JWT in a cookie scoped to your domain.