> ## Documentation Index
> Fetch the complete documentation index at: https://docs.minimo.it/llms.txt
> Use this file to discover all available pages before exploring further.

# Widget sessions and events

> Give each of your customers their own email area inside your SaaS: settings, sessions, permissions, embedding and the events your page receives

## Overview

If you run a SaaS on top of Minimo (one Minimo workspace for your SaaS, your customers as **tenants** inside it, see [How embedded Minimo works](https://docs.minimo.it/guides/embedded-overview.md)), the embedded widgets give each customer a self-service area **inside your product, without a Minimo account**:

| Widget | Permission | In the default | Page |
| - | - | - | - |
| Automatic messages (with the email editor and the on/off switches) | `templates` | yes | [Automatic messages widget](https://docs.minimo.it/guides/embedded-widget-automatic-messages.md) |
| Campaigns | `campaigns` (+ `contacts` to pick and add recipients) | yes | [Campaigns widget](https://docs.minimo.it/guides/embedded-widget-campaigns.md) |
| Send history | `history` | yes | [Send history widget](https://docs.minimo.it/guides/embedded-widget-history.md) |
| Statistics | `stats` | yes | [Statistics widget](https://docs.minimo.it/guides/embedded-widget-statistics.md) |
| Brand | `brand` | no, ask for it | [Brand widget](https://docs.minimo.it/guides/embedded-widget-brand.md) |
| WhatsApp | `whatsapp` | no, ask for it | [WhatsApp widget](https://docs.minimo.it/guides/embedded-widget-whatsapp.md) |

How it works:

1. your **backend** asks Minimo for a widget session for one tenant;
2. your **frontend** puts the widget URLs in iframes (one line of JavaScript);
3. your page receives **events** (`template.saved`, `campaign.sent`, …) through `postMessage`.

Your API key never reaches the browser. Every widget call is limited to the tenant of the session: a tenant never sees another tenant's templates, contacts or sends.

> [!NOTE]
>
> The widgets are in English and Italian, with a neutral look and a small "Email by Minimo" line (hide it with
> `poweredBy: false` for a white-label look).

## Before you start

* An API key with the **Widgets** permission (`widgets`), kept on your server. See [API keys and permissions](https://docs.minimo.it/guides/embedded-overview.md#api-keys-and-permissions).
* The **origins** of the pages that embed the widgets and the **templates** your tenants see, set once:

```bash theme={null}
curl -X PUT https://api.minimo.it/public/v1/widgets/settings \
  -H "Authorization: Bearer YOUR_MINIMO_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "allowedOrigins": ["https://app.yoursaas.example"],
    "masterTemplateIds": [891, 892, 893]
  }'
```

```json theme={null}
{ "data": { "allowedOrigins": ["https://app.yoursaas.example"], "masterTemplateIds": [891, 892, 893] } }
```

| Field | Notes |
| - | - |
| `allowedOrigins` | Pages allowed to embed the widgets (`frame-ancestors`) and to receive their events. Same list as [embedded domain connect](https://docs.minimo.it/guides/embedded-domain-connect.md). Exact `scheme://host[:port]`, `https` only (`http` for `localhost`), no wildcards, at most 20. Anything else answers `400` with a `userMessage`. |
| `masterTemplateIds` | Numeric template ids, at most 50. Your **transactional** templates are the tenants' automatic messages; your **campaign** templates are the starters of their broadcasts. A utility template that reads like a promotion (review request, offer, newsletter) is refused with `422 MASTER_LOOKS_PROMOTIONAL`: set its category to marketing. |

Each field replaces its list; omit a field to keep it. `GET` on the same path reads the settings.

## 1. Create a session (your backend)

```bash theme={null}
curl -X POST https://api.minimo.it/public/v1/widgets/sessions \
  -H "Authorization: Bearer YOUR_MINIMO_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "tenant": "restaurant_4812",
    "tenantName": "Trattoria da Mario",
    "locale": "it",
    "variables": { "RESTAURANT_NAME": "Trattoria da Mario", "LOGO_URL": "https://cdn.example.com/mario/logo.png" },
    "from": "Trattoria da Mario <news@trattoriadamario.yoursaas.example>"
  }'
```

| Field | Required | Notes |
| - | - | - |
| `tenant` | yes | Your reference for the customer, the same value you pass as `tenant` when you send. 1-64 chars: letters, digits, `.` `_` `:` `-`. |
| `tenantName` | no | Shown in the widgets. |
| `permissions` | no | Any of `templates`, `campaigns`, `contacts`, `history`, `stats`, `brand`, `whatsapp`. Default: `templates`, `campaigns`, `contacts`, `history`, `stats`. A list you pass is taken as is (nothing is added to it). `brand` and `whatsapp` are never in the default: ask for them explicitly. |
| `locale` | no | `en` (default) or `it`. |
| `variables` | no | The tenant's merge values (`{RESTAURANT_NAME}`…), used in previews and in their broadcasts. Up to 30 keys, values up to 500 characters. |
| `from` | no | Sender of the tenant's broadcasts. Must be under a verified domain of your workspace (a subdomain works too), otherwise `422`. |
| `expiresInMinutes` | no | 5 to 720, default 60. |
| `poweredBy` | no | Default `true`: a small "Email by Minimo" line at the bottom of the widget pages, like the domain popup. `false` = white label: the pages carry no Minimo name. The emails your tenants send never carry a Minimo badge either way. |
| `brand.locked` | no | Brand fields you keep for yourself, read-only in the [Brand widget](https://docs.minimo.it/guides/embedded-widget-brand.md): any of `displayName`, `legalName`, `logoUrl`, `brandColor`, `address`, `phone`, `email`, `website`, `privacyUrl`, `socials`. |

Response (`201`):

```json theme={null}
{
  "data": {
    "sessionId": "00000000-0000-4000-8000-000000000001",
    "token": "mws_…",
    "expiresAt": "2026-11-08T19:45:33.457Z",
    "tenant": "restaurant_4812",
    "urls": {
      "templates": "https://api.minimo.it/public/v1/embed/widgets/templates?s=…#mws_…",
      "campaigns": "https://api.minimo.it/public/v1/embed/widgets/campaigns?s=…#mws_…",
      "history": "https://api.minimo.it/public/v1/embed/widgets/history?s=…#mws_…",
      "brand": "https://api.minimo.it/public/v1/embed/widgets/brand?s=…#mws_…",
      "whatsapp": "https://api.minimo.it/public/v1/embed/widgets/whatsapp?s=…#mws_…",
      "stats": "https://api.minimo.it/public/v1/embed/widgets/stats?s=…#mws_…"
    }
  }
}
```

Mint a session when your customer opens the page and pass only the `urls` to the browser. The token travels in the URL fragment, which never reaches a server log.

* Every URL is returned. A widget whose permission is not in the session loads, but its calls are refused (`403`) and it shows an error: mount only the widgets you asked for.
* Every session also refreshes the [tenant profile](https://docs.minimo.it/guides/embedded-widget-brand.md#tenant-profile) from `tenantName`, `locale` and the brand variables, except the fields the restaurant changed itself.
* Revoke a session early with `DELETE /public/v1/widgets/sessions/{sessionId}` (`204`; `404` once it is gone). A revoked or expired session shows "Session expired: reload the page."
* An invalid body (unknown permission, bad `tenant`) answers `400`.

## 2. Embed the widgets (your frontend)

```html theme={null}
<div id="minimo-templates"></div>
<script src="https://api.minimo.it/public/v1/embed/widgets/widgets.js"></script>
<script>
  Minimo.mountWidget({
    url: session.urls.templates,
    container: '#minimo-templates',
    minHeight: 760,
    onEvent: function (event) {
      console.log(event.widget, event.type, event.data);
    },
  });
</script>
```

| Option | Notes |
| - | - |
| `url` | Required. One of the session `urls`. |
| `container` | Required. A CSS selector or an element. |
| `minHeight` | Minimum height in pixels, default 480. |
| `autoHeight` | Default `true`: the iframe follows the height of its content. |
| `title` | Title of the iframe for screen readers, default "Email". |
| `onEvent` | Called with `{ widget, type, data }` for every event of this iframe. |

`mountWidget` returns `{ iframe, destroy() }`. A plain `<iframe src="…">` works too.

## Events

Every message is `{ source: 'minimo-widget', widget, type, data }`, posted only to your allowed origins.

| `widget` | `type` | `data` |
| - | - | - |
| any | `ready` | `count` (items shown) |
| any | `resize` | `height` (handled by `mountWidget`) |
| any | `error` | `message` or `code` |
| `templates` | `template.customized` | `templateId` (the tenant copy), `masterId` |
| `templates` | `template.reset` | `masterId` |
| `templates` | `template.previewed`, `editor.opened` | `templateId` |
| `templates` | `automation.toggled` | `tenant`, `rule`, `enabled`: the tenant switched an [automatic send](https://docs.minimo.it/guides/embedded-automations.md) on or off (saved by Minimo) |
| `editor` | `ready`, `template.saved`, `closed` | `templateId` |
| `campaigns` | `campaign.created` | `campaignId` |
| `campaigns` | `contact.added` | `contactId` |
| `campaigns` | `campaign.sent` | `campaignId`, `status`, `submissionId`, `recipients` |
| `brand` | `ready` | `tenant` |
| `brand` | `brand.saved` | `tenant`, `fields` (the fields the restaurant changed, e.g. `logoUrl`, `brandColor`), `appliedTo` (personalized messages recolored) |
| `stats` | `ready` | `period` (7, 30 or 90), `sent` |
| `whatsapp` | `ready` | `tenant`, `status` (`not_connected`, `connecting`, `connected`, `error`) |
| `whatsapp` | `whatsapp.connected` | `tenant`, `number` |
| `whatsapp` | `whatsapp.error` | `tenant`, `code` (see [WhatsApp widget](https://docs.minimo.it/guides/embedded-widget-whatsapp.md#errors)) |
| `whatsapp` | `whatsapp.connect_requested` | `tenant`, `url`: the browser blocked the setup window, open `url` yourself from a click |

Events are a convenience for your UI. To know the real state from your backend, use the API (tenant profile, rules, statistics, message list).

## What a tenant can and cannot do

* **Automatic messages**: personalize a copy of each master (texts, colors, images, subject), reset it to the standard, switch each automatic send on or off. Merge variables stay as they are. The footer with the unsubscribe link and the sender identity cannot be removed.
* **Campaigns**: create from a starter, edit, send now, only to their own contacts who gave marketing consent. A sent campaign cannot be edited or sent again.
* **Contacts**: add contacts from the campaign widget. A contact belongs to a tenant through the tag `tenant:<tenant>` in your workspace. A guest already known to another tenant is linked, never modified, and shown by email only.
* **Brand**: set its own logo, color, contacts and legal details, except the fields you lock.
* **WhatsApp**: connect their own number (their own WhatsApp Business account and card on Meta) and see its state. A number already connected to Minimo is refused, never moved.
* Your workspace's sending rules apply to every tenant (consent, review of the first campaigns, daily limits for new tenants), see [Protections and sending limits](https://docs.minimo.it/guides/embedded-protections.md). The usage counts on your workspace.

## Security

* The API key stays on your server; the session token is stored hashed (SHA-256) and expires.
* Every widget request is pinned to the session's workspace **and tenant**; another tenant's ids answer `404`.
* The editor opens through a single-use link bound to one template.
* `frame-ancestors` and `postMessage` targets are your allowed origins only.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.