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

# Bot Pairing

> Secure unknown user onboarding with CLI-approved pairing codes

Bot pairing lets unknown users self-request access to your bot with secure 8-character codes that you approve from the CLI.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    subgraph "Pairing Flow"
        A[👤 Unknown User] --> B[🤖 Bot DM]
        B --> C[🔐 Generate Code]
        C --> D[📱 Send Code]
        D --> E[💻 CLI Approve]
        E --> F[✅ User Paired]
    end
    
    classDef user fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef bot fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef code fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef cli fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef success fill:#10B981,stroke:#7C90A0,color:#fff
    
    class A user
    class B bot
    class C,D code
    class E cli
    class F success
```

## Quick Start

<Steps>
  <Step title="Enable Pairing Policy">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from praisonaiagents import Agent
    from praisonaiagents.bots import BotConfig

    config = BotConfig(
        allowed_users=["@owner"],
        unknown_user_policy="pair",  # Enable pairing for unknown users
    )

    # Bot setup with Telegram adapter (other platforms pending)
    # from praisonai.bots.telegram import TelegramAdapter
    # bot = TelegramAdapter(config)
    ```
  </Step>

  <Step title="Approve Pairing Requests">
    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    # List pending pairing requests
    praisonai pairing list

    # Approve a user's pairing code
    praisonai pairing approve telegram ABCD1234 --label "alice"

    # View all paired channels
    praisonai pairing list
    ```
  </Step>
</Steps>

***

## How It Works

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant User
    participant Bot
    participant Handler
    participant PairingStore
    participant Owner
    
    User->>Bot: DM message
    Bot->>Handler: Check if user allowed
    Handler->>PairingStore: is_paired(channel_id)?
    PairingStore-->>Handler: No
    Handler->>PairingStore: generate_code(telegram, channel_id)
    PairingStore-->>Handler: ABCD1234
    Handler->>Bot: Send pairing instructions
    Bot->>User: "Code: ABCD1234"
    
    Note over Owner: Receives notification
    Owner->>PairingStore: praisonai pairing approve telegram ABCD1234
    PairingStore->>PairingStore: Mark channel as paired
    
    User->>Bot: Next DM
    Bot->>Handler: Check if user allowed
    Handler->>PairingStore: is_paired(channel_id)?
    PairingStore-->>Handler: Yes
    Handler-->>Bot: Allow message
    Bot->>User: Normal response
```

***

## Policy Configuration

### Unknown User Policies

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents.bots import BotConfig

# Policy options
config = BotConfig(
    allowed_users=["@owner", "123456789"],
    unknown_user_policy="pair"  # Choose one below
)
```

| Policy    | Behavior                                   | Use Case              |
| --------- | ------------------------------------------ | --------------------- |
| `"deny"`  | Silently drop messages (default)           | Private bots, testing |
| `"allow"` | Allow all users (overrides allowed\_users) | Public bots           |
| `"pair"`  | Use pairing flow for approval              | Controlled access     |

### Pairing Rate Limiting

The system includes built-in protection against code generation spam:

* **Rate Limit**: 10 minutes between code generations per channel
* **Code TTL**: Codes expire after a configurable time (default: check `PaisingStore` implementation)
* **Automatic Cleanup**: Stale rate limit entries are automatically evicted

***

## CLI Commands

### List Commands

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# List all paired channels
praisonai pairing list

# Example output:
# Found 3 paired channels:
#
# Platform: telegram
# Channel:  @alice_username  
# Paired:   2026-04-22 10:30:15
# Label:    alice
#
# Platform: telegram
# Channel:  987654321
# Paired:   2026-04-22 11:45:22
# Label:    bob
```

### Approve Command

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Approve with auto-resolved channel ID (when code is bound)
praisonai pairing approve telegram ABCD1234

# Approve with explicit channel ID
praisonai pairing approve telegram ABCD1234 987654321

# Approve with human-readable label
praisonai pairing approve telegram ABCD1234 --label "alice"

# Use custom store directory
praisonai pairing approve telegram ABCD1234 --store-dir /path/to/store
```

### Revoke Access

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Revoke specific channel
praisonai pairing revoke telegram 987654321

# Clear all pairings (with confirmation)
praisonai pairing clear
# Are you sure you want to clear ALL paired channels? [y/N]: y
# ✅ Cleared 3 paired channels

# Clear without confirmation prompt
praisonai pairing clear --confirm
```

***

## Web-UI / HTTP API Approval

Approve, list, and revoke pairings from a web admin UI via the gateway's HTTP API.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant Browser as Browser (Chainlit/Admin UI)
    participant API as POST /api/pairing/approve
    participant Store as PairingStore
    participant Bus as EventBus
    participant Bot
    
    Browser->>API: Approve pairing request
    API->>Store: approve(channel, code)
    Store-->>API: Success
    API->>Bus: pairing_approved event
    Bus->>Bot: Event notification
    Bot-->>Browser: User acknowledged
    
    classDef user fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef api fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef event fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef success fill:#10B981,stroke:#7C90A0,color:#fff
    
    class Browser user
    class API,Store api
    class Bus event
    class Bot success
```

### Endpoints

<Tabs>
  <Tab title="List Pending">
    **GET /api/pairing/pending**

    Returns pending pairing requests awaiting approval.

    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    curl -X GET "http://localhost:8000/api/pairing/pending" \
      -H "Cookie: session=<admin-session-cookie>"
    ```

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    import httpx

    resp = httpx.get(
        "http://localhost:8000/api/pairing/pending",
        cookies={"session": "<admin-session-cookie>"},
    )
    resp.raise_for_status()
    print(resp.json())  # {"pending": [{"channel_type": "ui", "code": "ABCD1234", "channel_id": "user123", "age_seconds": 45}]}
    ```
  </Tab>

  <Tab title="Approve Pairing">
    **POST /api/pairing/approve**

    Approve a pairing code to authorize the channel.

    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    curl -X POST "http://localhost:8000/api/pairing/approve" \
      -H "Content-Type: application/json" \
      -H "Cookie: session=<admin-session-cookie>" \
      -d '{"channel": "ui", "code": "ABCD1234"}'
    ```

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    import httpx

    # Admin operator approves a pending UI pairing
    resp = httpx.post(
        "http://localhost:8000/api/pairing/approve",
        json={"channel": "ui", "code": "ABCD1234"},
        cookies={"session": "<admin-session-cookie>"},
    )
    resp.raise_for_status()
    print(resp.json())  # {"approved": True, "channel": "ui", "code": "ABCD1234"}
    ```
  </Tab>

  <Tab title="Revoke Access">
    **POST /api/pairing/revoke**

    Revoke authorization for a previously paired channel.

    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    curl -X POST "http://localhost:8000/api/pairing/revoke" \
      -H "Content-Type: application/json" \
      -H "Cookie: session=<admin-session-cookie>" \
      -d '{"channel": "ui", "user_id": "user123"}'
    ```

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    import httpx

    resp = httpx.post(
        "http://localhost:8000/api/pairing/revoke",
        json={"channel": "ui", "user_id": "user123"},
        cookies={"session": "<admin-session-cookie>"},
    )
    resp.raise_for_status()
    print(resp.json())
    ```
  </Tab>
</Tabs>

<Note>
  All three routes are gated by an admin auth checker (`auth_admin`) wired in by `create_pairing_routes(pairing_store, auth_admin)`. Non-admin sessions receive `403`. Invalid codes return `404`.
</Note>

<Warning>
  The `code` parameter on `/approve` is consumed atomically — replays return `404`.
</Warning>

***

## Reacting to Pairing Approvals (EventBus)

Subscribe to the `pairing_approved` event to run agent logic the moment a channel is approved — for example, send a welcome DM, log to your CRM, or warm a per-user memory store.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    A[Approve API] --> B[EventBus]
    B --> C[Agent Handler]
    
    classDef api fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef event fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef handler fill:#10B981,stroke:#7C90A0,color:#fff
    
    class A api
    class B event
    class C handler
```

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent
from praisonaiagents.bus import get_default_bus

welcomer = Agent(
    name="Welcomer",
    instructions="Write a one-line friendly welcome message for a new user.",
)

def on_pairing_approved(event):
    channel = event.data["channel"]
    code = event.data["code"]
    welcome = welcomer.start(f"Welcome the new {channel} user with code {code}")
    print(welcome)

get_default_bus().subscribe(on_pairing_approved, ["pairing_approved"])
```

### Event Payload

| Field                   | Type  | Description                                          |
| ----------------------- | ----- | ---------------------------------------------------- |
| `event.type`            | `str` | Always `"pairing_approved"`                          |
| `event.data["channel"]` | `str` | Channel type (e.g., `"telegram"`, `"ui"`, `"slack"`) |
| `event.data["code"]`    | `str` | The pairing code that was approved                   |

<Tip>
  Handlers run synchronously in the request path — keep them fast or hand off to a background queue.
</Tip>

***

## Platform Support

### Current Implementation

| Platform              | Status     | Handler Wiring               | CLI Support |
| --------------------- | ---------- | ---------------------------- | ----------- |
| **Telegram**          | ✅ Shipped  | ✅ Complete                   | ✅ Full      |
| **Web UI (HTTP API)** | ✅ Shipped  | ✅ via /api/pairing/\* routes | ✅ Full      |
| **Discord**           | 🔧 Pending | ❌ Not wired                  | ✅ CLI ready |
| **Slack**             | 🔧 Pending | ❌ Not wired                  | ✅ CLI ready |
| **WhatsApp**          | 🔧 Pending | ❌ Not wired                  | ✅ CLI ready |

<Note>
  **Platform Implementation Status**: PR #1504 ships the pairing system and CLI with full Telegram support. Other platform adapters need handler wiring to complete the integration.
</Note>

### Telegram Integration

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Telegram adapter includes UnknownUserHandler wiring
from praisonai.bots.telegram import TelegramAdapter
from praisonaiagents.bots import BotConfig

config = BotConfig(
    token=os.getenv("TELEGRAM_BOT_TOKEN"),
    unknown_user_policy="pair"
)

# Handler automatically wired in Telegram adapter
bot = TelegramAdapter(config)
```

***

## Security Model

### Code Generation

* **8-character codes**: Hex format (e.g., `ABCD1234`)
* **HMAC signatures**: Codes are cryptographically signed
* **Per-install secret**: Auto-generated if `PRAISONAI_GATEWAY_SECRET` unset
* **Channel binding**: Codes can be bound to specific channel IDs

### Secret Management

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Option 1: Set explicit gateway secret (recommended for production)
export PRAISONAI_GATEWAY_SECRET="your-secure-secret-key"

# Option 2: Auto-generated per-install secret
# Stored at <store_dir>/.gateway_secret with 0600 permissions
# Persists across restarts, unique per installation
```

<Warning>
  **Secret Persistence**: Without `PRAISONAI_GATEWAY_SECRET`, a per-install secret is auto-generated and stored at `<store_dir>/.gateway_secret` with mode `0600`. This file is critical for code verification across restarts.
</Warning>

### Security Features

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph TD
    A[Code Request] --> B{Rate Limited?}
    B -->|Yes| C[Drop Request ❌]
    B -->|No| D[Generate HMAC Code]
    D --> E[Store with TTL]
    E --> F[Send to User]
    F --> G[Owner Approval]
    G --> H{Valid Code?}
    H -->|Yes| I[Mark Paired ✅]
    H -->|No| J[Reject ❌]
    
    classDef security fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef process fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef success fill:#10B981,stroke:#7C90A0,color:#fff
    
    class A,B,D,G,H security
    class E,F process
    class I success
    class C,J security
```

1. **Rate Limiting**: 600s (10 min) window per channel prevents spam
2. **HMAC Verification**: Codes are cryptographically signed and verified
3. **TTL Expiration**: Codes automatically expire after configured time
4. **Atomic Operations**: Pairing state persisted atomically to disk

***

## User Interaction Flow

### Step-by-Step Process

1. **Unknown User DMs Bot**
   ```
   Unknown User: Hello!
   ```

2. **Bot Generates Pairing Code**
   ```
   Bot: Your pairing code: `ABCD1234`
   Owner: `praisonai pairing approve telegram ABCD1234`
   ```

3. **Owner Approves via CLI**
   ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
   $ praisonai pairing approve telegram ABCD1234 --label "alice"
   ✅ Successfully paired telegram channel 987654321
      Label: alice
   ```

4. **User Can Now Interact Normally**
   ```
   Unknown User: Hello!
   Bot: Hi there! How can I help you today?
   ```

### Rate Limit Handling

If a user tries to generate codes too frequently:

```
User: Hello!
Bot: [no response - rate limited]

# In logs:
# DEBUG: Rate limited channel 987654321 (last code: 120.5s ago)
```

The user must wait for the rate limit window (10 minutes) to expire before requesting a new code.

***

## Configuration Options

### Store Directory

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Default: ~/.praisonai/pairing/
praisonai pairing list

# Custom directory
praisonai pairing list --store-dir /custom/path

# All commands support --store-dir
praisonai pairing approve telegram ABCD1234 --store-dir /custom/path
```

### Environment Variables

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Explicit gateway secret (recommended for production)
export PRAISONAI_GATEWAY_SECRET="your-256-bit-secret"

# Custom store directory (optional)
export PRAISONAI_STORE_DIR="/custom/store/path"
```

***

## Best Practices

<AccordionGroup>
  <Accordion title="Production Secret Management" icon="key">
    Set `PRAISONAI_GATEWAY_SECRET` explicitly in production environments to ensure consistent code verification across deployments.

    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    # Generate secure secret
    openssl rand -hex 32 > gateway_secret.txt

    # Set in production
    export PRAISONAI_GATEWAY_SECRET=$(cat gateway_secret.txt)
    ```
  </Accordion>

  <Accordion title="Monitor Rate Limits" icon="gauge">
    Watch for rate limiting warnings in logs - they indicate potential pairing spam or legitimate users hitting limits.

    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    # Look for these log patterns:
    # DEBUG: Rate limited channel 123456 (last code: 45.2s ago)
    # INFO: Generated pairing code for user123 on telegram: ABCD1234
    ```
  </Accordion>

  <Accordion title="Use Descriptive Labels" icon="tag">
    Add labels when approving pairings to identify channels later.

    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    # Good - easy to identify
    praisonai pairing approve telegram ABCD1234 --label "alice-work"
    praisonai pairing approve telegram EFGH5678 --label "bob-personal"

    # Less helpful
    praisonai pairing approve telegram ABCD1234
    ```
  </Accordion>

  <Accordion title="Regular Pairing Audits" icon="list-check">
    Periodically review paired channels and revoke access for inactive users.

    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    # Review all pairings
    praisonai pairing list

    # Revoke specific channels  
    praisonai pairing revoke telegram 987654321

    # Clear all if starting fresh
    praisonai pairing clear --confirm
    ```
  </Accordion>
</AccordionGroup>

***

## Related

<CardGroup cols={2}>
  <Card title="Bot Security" icon="shield-check" href="/docs/best-practices/bot-security">
    Comprehensive bot security and DM policies
  </Card>

  <Card title="Messaging Bots" icon="message" href="/docs/features/messaging-bots">
    Bot platform setup and configuration
  </Card>
</CardGroup>
