# ส่ง event เข้า AllClearly Monitoring

Monitoring (SIEM-lite) ใช้ได้ในแพ็กเกจ Pro และ Agency ระบบของคุณส่ง event สำคัญด้านความปลอดภัย (HTTP request, การ login) มาที่ AllClearly แล้วเราจะแจ้งเตือนทาง LINE เมื่อเจอสิ่งผิดปกติ เช่น มีคนเดารหัสผ่าน มีบอทหาช่องโหว่ หรือมีการ login จากประเทศที่ไม่เคยเห็น

## สร้าง token

ไปที่ workspace → แท็บ **Monitoring** → ใส่ชื่อระบบแล้วกด **เชื่อมระบบ** จากนั้นคัดลอก token (ขึ้นต้นด้วย `acm_`) token จะแสดงครั้งเดียว ถ้าหายให้กด **สร้าง token ใหม่** (token เดิมจะใช้ไม่ได้ทันที)

ถ้าใช้ AI ช่วยเขียนโค้ด (Claude Code, Cursor ฯลฯ) กด **คัดลอก prompt เชื่อมต่อ** ที่แถวของระบบนั้น แล้ววางให้ AI โค้ดที่ได้จะอ่าน token จาก `ALLCLEARLY_MONITOR_TOKEN`

## ใช้ SDK (แนะนำ)

SDK รวม event เป็นชุดแล้วส่งเบื้องหลัง ไม่ทำให้เว็บช้า ส่งไม่ได้ก็ไม่ทำให้เว็บพัง และไม่ส่งรหัสผ่าน query string หรือข้อความ error ตั้ง token ใน environment variable `ALLCLEARLY_MONITOR_TOKEN` แล้วติดตั้งตามภาษาที่ใช้

**PHP / Laravel** ([คู่มือ](https://github.com/hyphen-plus/monitor-php))

```bash
composer require allclearly/monitor
```

Laravel ใช้ได้ทันทีหลังติดตั้ง: ส่งทุก request (รวม 404), login สำเร็จ/ไม่สำเร็จ, สมัครสมาชิก และ error ให้อัตโนมัติ ตั้ง `ALLCLEARLY_ADMIN_ABILITY` เป็นชื่อ Gate ที่บอกว่าเป็นผู้ดูแล (ใช้ตรวจผู้ดูแล login จากประเทศใหม่)

**Node.js** (Express, Next.js, Bun, Cloudflare Workers) ([คู่มือ](https://www.npmjs.com/package/@allclearly/monitor))

```bash
npm install @allclearly/monitor
```

```js
import { AllClearly, expressMiddleware } from "@allclearly/monitor";

const monitor = new AllClearly();
app.use(expressMiddleware(monitor)); // ส่งทุก request
monitor.loginFailed({ ip: req.ip, user: email, path: "/login", status: 401 });
```

SDK ตั้งแต่ 0.2.0 เข้ารหัสทุกชุดก่อนส่ง (end-to-end) มีแค่ AllClearly ที่ถอดได้ CDN ระหว่างทางก็อ่านไม่ได้ รายละเอียดและ threat model อยู่ที่ [monitoring-encryption.md](monitoring-encryption.md)

ภาษาอื่นส่งตรงเข้า endpoint ด้านล่างได้ (ต้องเป็น https) ถ้า workspace เปิด **รับเฉพาะข้อมูลที่เข้ารหัส** ต้องเข้ารหัสตาม [monitoring-encryption.md](monitoring-encryption.md)

## Endpoint

```
POST https://allclearly.com/api/ingest/events
Authorization: Bearer acm_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
```

```json
{
  "events": [
    {
      "type": "auth.login_failed",
      "ts": "2026-10-07T03:00:00Z",
      "ip": "203.0.113.9",
      "user": "bob@shop.com",
      "country": "TH",
      "method": "POST",
      "path": "/login",
      "status": 401,
      "admin": false,
      "meta": {}
    }
  ]
}
```

ตอบกลับ `202 {"accepted": 1, "rejected": 0}` event ที่ไม่ผ่านเงื่อนไขจะถูกนับใน `rejected` และไม่ถูกเก็บ (event อื่นใน batch เดียวกันยังเก็บตามปกติ)

## ฟิลด์

| ฟิลด์ | ชนิด | จำเป็น | รายละเอียด |
|---|---|---|---|
| `type` | string | ใช่ | หนึ่งใน `http.request`, `auth.login_failed`, `auth.login_succeeded`, `auth.signup`, `auth.role_changed`, `app.error` หรือจาก agent บน Linux: `ssh.login_failed`, `ssh.login_succeeded`, `sudo.command`, `system.user_added`, `system.agent_test` |
| `ts` | string | ใช่ | เวลาที่เกิด ISO 8601 ต้องไม่เก่ากว่า 24 ชม. และไม่ล้ำอนาคตเกิน 5 นาที |
| `ip` | string | ไม่ | IPv4 หรือ IPv6 ของผู้ใช้ (ถ้าส่งมาแต่รูปแบบผิด event จะถูก reject) |
| `country` | string | ไม่ | รหัสประเทศ ISO 3166-1 alpha-2 เช่น `TH` (Cloudflare: header `CF-IPCountry`, Vercel: `x-vercel-ip-country`) |
| `method` | string | ไม่ | `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, `OPTIONS` |
| `path` | string | ไม่ | path ที่ถูกเรียก query string จะถูกตัดทิ้ง ยาวสุด 500 ตัวอักษร |
| `status` | integer | ไม่ | HTTP status 100–599 |
| `user` | string | ไม่ | อีเมลหรือ user id ระบบ hash ทันทีที่รับ (PDPA) เราไม่เก็บค่าจริง |
| `admin` | boolean | ไม่ | `true` ถ้าเป็นบัญชีผู้ดูแล ใช้ตรวจการ login จากประเทศใหม่ (M06) |
| `host` | string | ไม่ | ชื่อเครื่อง (A-Z a-z 0-9 . _ - ไม่เกิน 255 ตัว ผิดรูปแบบ = event ถูก reject) กฎของ Linux แยกแจ้งเตือนต่อเครื่อง |
| `meta` | object | ไม่ | ข้อมูลเพิ่มเติม ไม่เกิน 2 KB (เกินจะถูกตัดทิ้ง แต่ event ยังเก็บ) |

## ข้อจำกัด

- ไม่เกิน 500 events ต่อ request
- body ไม่เกิน 1 MB
- 120 requests ต่อนาทีต่อ token
- เก็บ event 30 วัน

## Status code

| Code | ความหมาย |
|---|---|
| 202 | รับแล้ว ดู `accepted` / `rejected` |
| 400 | `encryption_required`: workspace บังคับเข้ารหัส หรือ body ที่เข้ารหัสถอดไม่ได้ (ดู [monitoring-encryption.md](monitoring-encryption.md)) |
| 401 | token ไม่ถูกต้อง หรือถูกลบ/ออกใหม่แล้ว |
| 402 | workspace ไม่ได้อยู่ในแพ็กเกจ Pro/Agency |
| 403 | ส่งผ่าน http (ต้องเป็น https) |
| 413 | body ใหญ่เกิน 1 MB |
| 422 | `events` ว่าง ไม่ใช่ array หรือเกิน 500 รายการ |
| 429 | ส่งถี่เกิน รอแล้วลองใหม่ |

## ตัวอย่าง

curl:

```bash
curl -X POST https://allclearly.com/api/ingest/events \
  -H "Authorization: Bearer $ALLCLEARLY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"events":[{"type":"auth.login_failed","ts":"'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'","ip":"203.0.113.9","user":"bob@shop.com","path":"/login","status":401}]}'
```

Laravel (เช่นใน listener ของ `Illuminate\Auth\Events\Failed`):

```php
use Illuminate\Support\Facades\Http;

Http::withToken(config('services.allclearly.token'))
    ->timeout(3)
    ->post('https://allclearly.com/api/ingest/events', [
        'events' => [[
            'type' => 'auth.login_failed',
            'ts' => now()->toIso8601String(),
            'ip' => request()->ip(),
            'user' => $event->credentials['email'] ?? null,
            'country' => request()->header('CF-IPCountry'),
            'path' => '/'.request()->path(),
            'status' => 401,
        ]],
    ]);
```

ควรส่งแบบ queue หรือรวมเป็น batch เพื่อไม่ให้หน้า login ช้าลง
