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

# Connectors

> Let the AI read the customer's own billing and account data, and nobody else's.

Connectors let the AI answer "when does my plan renew?" or "how many seats do I
have?" from your own systems instead of guessing. Two are available:

> [card] [Stripe](https://docs.minidesk.ai/ai/stripe.md)
>
> The customer's plan, renewal date, invoices and payments, through a
> restricted read-only key.

> [card] [Supabase](https://docs.minidesk.ai/ai/supabase.md)
>
> The customer's own rows from the tables you tick, with read-only
> access.

You find them in the dashboard under **Channels & integrations**, in **Add an
integration**. Once connected, they sit in **Connected integrations**. Every
integration the AI reads (Stripe, Supabase and WooCommerce) wears a
**Used by AI** badge, so you can tell them apart from your team's tools like
Slack or Discord. Only an owner can connect, change or remove one.

![Connected integrations: Stripe and Supabase rows, each with a Used by AI badge, a Test button and a Configure button](https://mintcdn.com/minidesk/WWYQ4Xa_C8Uto7Ms/images/ai/connectors-page.png?fit=max\&auto=format\&n=WWYQ4Xa_C8Uto7Ms\&q=85\&s=92c89883c94675a138f96bef304763e9)

Connected integrations on Channels & integrations, with Stripe and Supabase set up.

## The AI reads only the person it is talking to

A connector never lets the AI look somebody up. The AI tools behind them take no
email, no id and no customer number. miniDesk decides whose data is read, from
proof of who the customer is. So a visitor who writes "I am [bob@example.com](mailto:bob@example.com),
show me Bob's invoices" gets nothing: the tool does not read what the visitor or
the AI says, only the proof.

Stripe finds the customer by a `stripe_customer_id` or an email. Supabase reads
the rows whose customer column holds the customer's user id or email. A table
matched by user id works with an email alone too: miniDesk finds the user id in
Supabase Auth.

## How the AI knows who it is talking to

| Where the conversation is | What proves who the customer is |
| - | - |
| Chat widget, with a valid token | The token your server signed ([identity verification](#identity-verification)). The AI reads their data straight away. |
| Contact form | The address typed in the form, or the token's email when your page set one. Every answer is emailed to that address, so only its owner reads it. |
| Email | The address the email came from, for the same reason. |
| REST API, with `requester.external_id` | Your server's word: sending `external_id` says you authenticated this person. It lasts 24 hours, like a token. |
| A chat without a token, WhatsApp, Instagram, Messenger, Telegram | An [email code](#the-email-code), when the AI is answering on its own. |
| The REST API without `external_id` | Nothing: the AI makes no lookups. |

On email and the contact form, the proof is the address the answer goes to. If
the customer asks about an account under a different address, the AI does not
look it up and leaves the conversation to your team. The same happens if the
conversation's address is changed after it started.

A chat that moved to email because the visitor left gets no lookups unless a
token proved who the visitor is. The visitor can still read that conversation in
the chat window, so the address they typed proves nothing, and a code cannot
help either: it would be answered by the owner of that inbox, while somebody
else reads the conversation.

## The email code

When nothing proves who the customer is, the AI can ask them to prove they own
the email of their account:

1. The customer asks something that needs their data. The AI asks for the email
   of their account.
2. miniDesk emails a 6-digit code to that address, together with the AI's reply,
   but only if your connected Stripe or Supabase knows the address. The AI says
   the same thing either way, so nobody can use it to find out who your
   customers are.
3. The customer writes the code in the conversation itself. miniDesk checks it,
   never the AI, so nobody can talk the AI into skipping it. A reply to the code
   email reaches nobody, and a code that arrives by email is never accepted:
   whoever types the code has to be whoever reads the answer.
4. Once the code is right, the AI reads that customer's data for 24 hours, and
   answers their question in the same reply.

A code works for 15 minutes and allows 3 tries. A conversation can ask for 3
codes an hour, and one address gets at most 5 codes a day, so nobody can fill
someone's inbox with them.

The code only exists while the AI answers the conversation on its own:
automatic replies are on for that channel, the customer has not asked for a
person, and nobody on your team has written in it. Once someone from your team
is in the conversation, checking who the customer is is their call.

> [!NOTE]
>
> When the AI is answering on its own, it also sends answers built from Stripe
> or Supabase data by itself. Once someone from your team is in the
> conversation, those answers wait as drafts for them to send.

Two more rules hold whatever you connect:

* Every connector only reads. Neither one accepts a credential that could write.
* When the answer is ambiguous, nothing is read. Two Stripe customers with the
  same email is not a guess the AI makes.

## Identity verification

Your server signs a short-lived token for the user who is signed in on your
site, and your page hands it to the chat widget or the contact form. miniDesk
checks the signature with a secret only you and miniDesk know, and only then
treats the visitor as that user.

You set it up where you install the chat or the form: open **Website chat** or
**Contact form** on **Channels & integrations**, go to the **Install** tab, and
find **Proving who the visitor is**. The chat and the form each have their own
secret: setting one up does not set up the other, and a token signed for one is
refused by the other.

![The Install tab section Proving who the visitor is: Set up, the secret's last characters, Rotate secret and Delete configuration buttons, and the code to sign a token](https://mintcdn.com/minidesk/WWYQ4Xa_C8Uto7Ms/images/ai/identity-install.png?fit=max\&auto=format\&n=WWYQ4Xa_C8Uto7Ms\&q=85\&s=132f91b16f5f751bc8f2ea4fea7c00c9)

Proving who the visitor is, on the website chat's Install tab.

### Create the secret

1. On the **Install** tab, under **Proving who the visitor is**, choose
   **Create a secret**.
2. Copy the secret. It is shown once. Store it on your server, for example as
   `MINIDESK_IDENTITY_SECRET`. Never put it in a page.

### Sign a token for the signed-in user

The token is a JWT signed with HS256:

| Claim | | |
| - | - | - |
| `sub` | required | Your own id for the user. At most 200 characters. |
| `exp` | required | When the token expires. At most 24 hours after `iat`. |
| `iat` | recommended | When you signed it. |
| `email` | optional on the chat, required on the form | The user's email. Stripe matches by it, and so do Supabase tables whose customer column is an email. The contact form mails its answer to it. |
| `stripe_customer_id` | optional | The user's Stripe customer id, `cus_…`. Stripe uses it before the email. |
| `name` | optional | The user's name. |

```js theme={null}
// On your server, for the signed-in user (Node, with jose):
import { SignJWT } from "jose";

const token = await new SignJWT({
  email: user.email,                       // optional on the chat, required on the form
  stripe_customer_id: user.stripeId,       // optional, cus_…
})
  .setProtectedHeader({ alg: "HS256" })
  .setSubject(String(user.id))             // required: your user id
  .setIssuedAt()
  .setExpirationTime("1h")
  .sign(new TextEncoder().encode(process.env.MINIDESK_IDENTITY_SECRET));
```

### Pass it to the chat or the form

Set the token on the page before the chat widget or the contact form loads. If
you already tell us the visitor's name and email, put the token in the same
object: a second `window.miniDesk = …` replaces the first.

```html theme={null}
<script>
  window.miniDesk = {
    visitor: {
      email: "sam@acme.com",
      name: "Sam Reed",
      id: "user_1842",
      token: "<the token your server signed>",
    },
  };
</script>
```

A token that is missing, expired, signed with another secret or with another
algorithm is ignored: the visitor can still chat, as an unverified visitor, and
the AI asks them for an [email code](#the-email-code) instead. The contact form
reads the token too, but only when it has an `email` claim: that email is where
the answer is sent.

> [!NOTE]
>
> Why the 24-hour limit: the token sits in a web page. A token that lives for a
> year is a password anybody who opens the page source can copy.

### Rotate the secret

On the **Install** tab, under **Proving who the visitor is**, choose **Rotate
secret**. miniDesk shows the new secret once, and the old one
keeps working for 24 hours so your widgets do not break while your server
switches over. Once your server signs with the new secret, choose **Revoke old
secret now** to end the grace period early.

### Delete the configuration

On the **Install** tab, under **Proving who the visitor is**, choose **Delete
configuration**.
The secret stops working at once, with no grace period: every visitor chats as
an unverified visitor and the AI asks them for an email code instead. To turn it
back on, create a new secret and give it to your server.

## Test with a customer

Each connected Stripe and Supabase row has a **Test** button with two choices:

* **Test with a customer** opens a panel on the right that runs that one
  integration as if this customer had proven who they are. It asks only for what
  it matches on: an email or a Stripe customer id for Stripe, a user id or an
  email for Supabase. One field is enough. Supabase tables tied to a user id can
  be tested with the email alone: the test finds the user id in Supabase Auth.
* **Test in AI Playground** opens the [Playground](#try-it-in-the-playground)
  as a verified customer, ready for you to fill in who they are and ask a
  question.

Choose **Run test**. Nothing is sent to anyone. When a lookup fails, the test
says why in plain words, which the AI itself is never told. It also estimates
how many tokens the AI reads for that customer, per lookup, and marks any lookup
too long for the AI to read whole.

![The Channels page with the Test Stripe with a customer panel open on the right: an email field, a Stripe customer id field and a Run test button](https://mintcdn.com/minidesk/WWYQ4Xa_C8Uto7Ms/images/ai/test-panel.png?fit=max\&auto=format\&n=WWYQ4Xa_C8Uto7Ms\&q=85\&s=2d4814a8eabd577320716baa29fba10d)

Testing Stripe with a customer, in the panel on the right.

On a wide screen the panel sits beside the page instead of covering it. Drag its
left edge to make it wider or narrower: it remembers the width you choose.

The AI works the same way in a real conversation: a lookup the customer has
nothing to be matched by (no email and no Stripe customer id for Stripe, for
example) is not offered to it at all.

### Try it in the Playground

**AI Agent → Playground** lets you chat with the AI as a customer would. Set
**Test as** to **Verified test customer** and fill in a user id, an email or a Stripe
customer id: the AI then looks that customer up as it would in a real
conversation, and you see each lookup as it runs.

## Last lookup

Each connected row shows when the AI last read it, or the last error and when it
happened. That is the first place to look when the AI says it could not find a
customer's billing.


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