# Contract: Webhook Funil Onboarding

**Endpoint**: `POST /api/v1/webhooks/onboard` (inalterado — feature 001)

**Auth**: Header `X-Webhook-Secret`

## Eventos adicionais (004)

Todos usam o mesmo `WebhookPayload`:

```json
{
  "event": "<event_type>",
  "domain": "exemplo.com",
  "session_id": "uuid-da-sessao",
  "data": { }
}
```

### onboarding.started

```json
{
  "event": "onboarding.started",
  "domain": "exemplo.com",
  "session_id": "abc-123",
  "data": { "step": "validate-domain" }
}
```

**Ticket**: não cria.

---

### domain.validated

```json
{
  "event": "domain.validated",
  "domain": "exemplo.com",
  "session_id": "abc-123",
  "data": {
    "carbonio_domain_exists": false,
    "cloudflare_auto_dns": true,
    "mail_aliases": ["www"]
  }
}
```

**Ticket**: não cria.

---

### dns.applied

```json
{
  "event": "dns.applied",
  "domain": "exemplo.com",
  "session_id": "abc-123",
  "data": {
    "records_applied": 5,
    "verification_ready": true
  }
}
```

**Ticket**: não cria.

---

### infra.synced

```json
{
  "event": "infra.synced",
  "domain": "exemplo.com",
  "session_id": "abc-123",
  "data": {
    "traefik": "ok",
    "cert": "ok",
    "nginx_vhost": "ok"
  }
}
```

**Ticket**: não cria.

---

### onboarding.completed

```json
{
  "event": "onboarding.completed",
  "domain": "exemplo.com",
  "session_id": "abc-123",
  "data": {
    "email": "admin@exemplo.com",
    "account_verified": true,
    "infra_status": "ok"
  }
}
```

**Ticket**: actualiza ticket existente (nota `ready_for_ops`); não cria novo.

---

### onboarding.failed

```json
{
  "event": "onboarding.failed",
  "domain": "exemplo.com",
  "session_id": "abc-123",
  "data": {
    "step": "create_account",
    "error": "CarbonioError message",
    "http_status": 400
  }
}
```

**Ticket**: cria ticket `open` prioridade alta (subject `[onboarding.failed] dominio.com`).

---

## Response (200 OK)

```json
{
  "status": "accepted",
  "event_id": 42,
  "ticket_id": null,
  "duplicate": false
}
```

## Idempotência

Chave: `(event, session_id, domain)` — duplicate=true, ticket_id existente se aplicável.

## API consulta (Ops)

### GET /api/v1/onboard/funnel

```json
{
  "window_hours": 48,
  "stages": {
    "started": 3,
    "domain_validated": 2,
    "dns_applied": 2,
    "account_created": 1,
    "infra_synced": 1,
    "completed": 1,
    "failed": 0
  },
  "active_sessions": [
    {
      "session_id": "abc-123",
      "domain": "exemplo.com",
      "current_stage": "dns_applied",
      "last_event_at": "2026-06-08T18:00:00Z",
      "ticket_id": 5
    }
  ]
}
```

### GET /api/v1/onboard/sessions/{session_id}/timeline

```json
{
  "session_id": "abc-123",
  "domain": "exemplo.com",
  "events": [
    { "event_type": "onboarding.started", "created_at": "...", "data": {} },
    { "event_type": "domain.validated", "created_at": "...", "data": {} }
  ]
}
```
