# Webhook Audit Logging

Webhook audit logging records semantic channel events in memory so operators can inspect recent activity from the Web UI. It is designed for real-time visibility, not long-term forensic storage.

## Data Flow

```mermaid
flowchart TD
    CH["<b>Channels</b><br/>Discord / Teams / Facebook / Telegram"] --> H["<b>Channel Handler</b>"]
    H --> REC["<b>runtimeinfo.RecordWebhookAudit</b>"]
    REC --> BUF["<b>runtimeinfo.WebhookAuditLogs</b><br/>In-memory ring buffer (newest 500)"]
    BUF --> API["<b>GET /api/audit</b>"]
```

## Data Structure

```go
type WebhookAuditLog struct {
    Timestamp  time.Time `json:"timestamp"`
    Channel    string    `json:"channel"`
    EventType  string    `json:"event_type"`
    AuthorID   string    `json:"author_id"`
    AuthorName string    `json:"author_name"`
    PeerID     string    `json:"peer_id"`
    PeerKind   string    `json:"peer_kind"`
    Preview    string    `json:"preview"`
    Status     string    `json:"status"`
}
```

`RecordWebhookAudit` sets `Timestamp` when missing, cuts `Preview` to its first 120 bytes followed by `…`, and keeps only the newest 500 records. The buffer is in memory: a restart empties it.

`Status` is `processed`, `error` (handling or answering the message failed), `duplicate` (a repeated Teams delivery that was ignored), `ignored`, or, for a Teams SSO attempt, `unimplemented`.

## Event Types

| Event type | Meaning |
|---|---|
| `chat` | Regular text message |
| `command` | Slash command or command-style message |
| `typing` | Typing indicator, usually ignored |
| `reaction` | Message reaction, usually ignored |
| `comment` | Conversation update or comment-style event |
| `login_attempt` | Teams SSO token exchange (recorded with channel `msteams_sso`) |
| `unknown` | Channel event that did not match a known class |

## API

```text
GET /api/audit?channel=facebook&event=chat&user=USER123&limit=50
```

All query parameters are optional.

| Parameter | Description |
|---|---|
| `channel` | Filter by channel: `discord`, `msteams`, `msteams_sso`, `facebook` or `telegram` |
| `event` | Filter by event type |
| `user` | Filter by author ID |
| `limit` | Maximum rows, 1 to 500 (default 100). A missing, invalid, zero, negative or larger value gives 100 |

Response:

```json
{
  "logs": [
    {
      "timestamp": "2026-06-20T08:45:00Z",
      "channel": "discord",
      "event_type": "chat",
      "author_id": "123456789",
      "author_name": "User",
      "peer_id": "987654321",
      "peer_kind": "direct",
      "preview": "Hello, can you help me?",
      "status": "processed"
    }
  ],
  "total": 1
}
```

## Recording Points

| File | Event source |
|---|---|
| `internal/channel/discord/discord_handler.go` | Discord messages |
| `internal/channel/discord/discord_slash.go` | Discord slash commands |
| `internal/channel/msteams/msteams.go` | Teams activities |
| `internal/channel/msteams/msteams_sso.go` | Teams SSO token exchange attempts (channel `msteams_sso`, event `login_attempt`) |
| `internal/channel/facebook/facebook.go` | Facebook messages |
| `internal/channel/telegram/telegram.go` | Telegram messages |

## Audit Lines in the Gateway Log

A separate trail, `internal/audit`, records destructive or administrative
actions as one line each in the gateway log:

```text
[audit] action=sop.delete actor=… role=… source=… ip=… resource=sop/<id> success=true
```

It is written for `sop.delete` (from the `sop` tool and the SOP API) and for
`sop.channel.pin` / `sop.channel.unpin` (the SOP channels API). Find them with
`grep '\[audit\]' gateway.log`. The last 500 are also kept in memory and
served by `GET /api/audit?kind=admin`, newest first, filtered by `?action=`,
`?actor=` and `?resource=` (a resource type such as `sop`) and limited by
`?limit=` (default 100); they do not survive a restart.

## Tests

```bash
go test ./internal/runtimeinfo -count=1
```

The runtimeinfo tests cover recording, timestamp defaults, preview truncation, ring buffer overflow, filters, and newest-first ordering.
