# Webhooks

Les événements sortants signés (crawl terminé, export prêt, recommandations prêtes, signal détecté). Gérés par les owners et admins de l’équipe.

## Lister les webhooks

`GET /v1/webhooks`

Les abonnements webhooks de l’équipe. Les secrets n’y sont jamais rendus.

- Scopes requis: `webhooks:read`
- Coût en débit: 1 unité(s)

### Réponses

- `200`: OK.
- `401`: Missing, invalid or expired token, or a member who left the team.
- `403`: The plan does not include the API (`api.not_in_plan`) or this verb
(`api.write_not_in_plan`), or the token lacks a scope
(`token.missing_scope`).

- `429`: The plan rate limit is exhausted for one window.

### Exemple de requête (cURL)

```bash
curl "https://api.nessflow.com/v1/webhooks" \
  -H "Authorization: Bearer $NESSFLOW_TOKEN"
```

### Exemple de réponse

```json
{
    "data": [
        {
            "id": "0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57",
            "url": "https://exemple.fr/",
            "description": "string",
            "events": [
                "crawl.completed"
            ],
            "active": true,
            "disabled_reason": "consecutive_failures",
            "disabled_at": "2026-10-01T09:30:00Z",
            "consecutive_failures": 42,
            "last_delivery_at": "2026-10-01T09:30:00Z",
            "secret_rotation_ends_at": "2026-10-01T09:30:00Z",
            "created_at": "2026-10-01T09:30:00Z"
        }
    ]
}
```

## Créer un webhook

`POST /v1/webhooks`

Abonne une URL HTTPS à des événements. La réponse porte le `secret` de signature, montré cette seule fois. L’URL est refusée quand elle ne désigne pas une adresse publique (`webhook.url_refused`). Compte dans `api.webhooks.max`.

- Scopes requis: `webhooks:write`
- Coût en débit: 1 unité(s)

### Corps de la requête

- `url` (string, requis): An HTTPS URL that resolves to a public address.
- `events` (string[], requis) [crawl.completed, crawl.failed, crawl.stopped, export.ready, recommendations.ready, signal.detected]
- `description` (string | null, facultatif)

### Réponses

- `201`: OK.
- `400`: Malformed request, such as an invalid pagination cursor (`pagination.invalid_cursor`).
- `401`: Missing, invalid or expired token, or a member who left the team.
- `403`: The plan does not include the API (`api.not_in_plan`) or this verb
(`api.write_not_in_plan`), or the token lacks a scope
(`token.missing_scope`).

- `422`: Invalid query or body values (`validation_failed`), with `errors` by field.
- `429`: The plan rate limit is exhausted for one window.

### Exemple de requête (cURL)

```bash
curl -X POST "https://api.nessflow.com/v1/webhooks" \
  -H "Authorization: Bearer $NESSFLOW_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://exemple.fr/","events":["crawl.completed"],"description":"string"}'
```

### Exemple de réponse

```json
{
    "data": {
        "id": "0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57",
        "url": "https://exemple.fr/",
        "description": "string",
        "events": [
            "crawl.completed"
        ],
        "active": true,
        "disabled_reason": "consecutive_failures",
        "disabled_at": "2026-10-01T09:30:00Z",
        "consecutive_failures": 42,
        "last_delivery_at": "2026-10-01T09:30:00Z",
        "secret_rotation_ends_at": "2026-10-01T09:30:00Z",
        "created_at": "2026-10-01T09:30:00Z",
        "secret": "string"
    }
}
```

## Lire un webhook

`GET /v1/webhooks/{webhook}`

Un abonnement, son état et son compteur d’échecs.

- Scopes requis: `webhooks:read`
- Coût en débit: 1 unité(s)

### Paramètres

- `webhook` (path, string (uuid), requis): The webhook identifier.

### Réponses

- `200`: OK.
- `401`: Missing, invalid or expired token, or a member who left the team.
- `403`: The plan does not include the API (`api.not_in_plan`) or this verb
(`api.write_not_in_plan`), or the token lacks a scope
(`token.missing_scope`).

- `404`: No such resource for the token's team. Resources of another team answer 404 as well, never 403.
- `429`: The plan rate limit is exhausted for one window.

### Exemple de requête (cURL)

```bash
curl "https://api.nessflow.com/v1/webhooks/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57" \
  -H "Authorization: Bearer $NESSFLOW_TOKEN"
```

### Exemple de réponse

```json
{
    "data": {
        "id": "0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57",
        "url": "https://exemple.fr/",
        "description": "string",
        "events": [
            "crawl.completed"
        ],
        "active": true,
        "disabled_reason": "consecutive_failures",
        "disabled_at": "2026-10-01T09:30:00Z",
        "consecutive_failures": 42,
        "last_delivery_at": "2026-10-01T09:30:00Z",
        "secret_rotation_ends_at": "2026-10-01T09:30:00Z",
        "created_at": "2026-10-01T09:30:00Z"
    }
}
```

## Modifier un webhook

`PATCH /v1/webhooks/{webhook}`

Change l’URL, les événements ou la description, ou active et suspend l’abonnement. Le réactiver lève une désactivation automatique et remet son compteur d’échecs à zéro.

- Scopes requis: `webhooks:write`
- Coût en débit: 1 unité(s)

### Paramètres

- `webhook` (path, string (uuid), requis): The webhook identifier.

### Corps de la requête

- `url` (string, facultatif)
- `events` (string[], facultatif) [crawl.completed, crawl.failed, crawl.stopped, export.ready, recommendations.ready, signal.detected]
- `description` (string | null, facultatif)
- `active` (boolean, facultatif)

### Réponses

- `200`: OK.
- `401`: Missing, invalid or expired token, or a member who left the team.
- `403`: The plan does not include the API (`api.not_in_plan`) or this verb
(`api.write_not_in_plan`), or the token lacks a scope
(`token.missing_scope`).

- `404`: No such resource for the token's team. Resources of another team answer 404 as well, never 403.
- `422`: Invalid query or body values (`validation_failed`), with `errors` by field.
- `429`: The plan rate limit is exhausted for one window.

### Exemple de requête (cURL)

```bash
curl -X PATCH "https://api.nessflow.com/v1/webhooks/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57" \
  -H "Authorization: Bearer $NESSFLOW_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://exemple.fr/","events":["crawl.completed"],"description":"string","active":true}'
```

### Exemple de réponse

```json
{
    "data": {
        "id": "0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57",
        "url": "https://exemple.fr/",
        "description": "string",
        "events": [
            "crawl.completed"
        ],
        "active": true,
        "disabled_reason": "consecutive_failures",
        "disabled_at": "2026-10-01T09:30:00Z",
        "consecutive_failures": 42,
        "last_delivery_at": "2026-10-01T09:30:00Z",
        "secret_rotation_ends_at": "2026-10-01T09:30:00Z",
        "created_at": "2026-10-01T09:30:00Z"
    }
}
```

## Supprimer un webhook

`DELETE /v1/webhooks/{webhook}`

Supprime l’abonnement et son journal de livraisons. Jamais refusé par la facturation.

- Scopes requis: `webhooks:write`
- Coût en débit: 1 unité(s)

### Paramètres

- `webhook` (path, string (uuid), requis): The webhook identifier.

### Réponses

- `204`: Deleted.
- `401`: Missing, invalid or expired token, or a member who left the team.
- `403`: The plan does not include the API (`api.not_in_plan`) or this verb
(`api.write_not_in_plan`), or the token lacks a scope
(`token.missing_scope`).

- `404`: No such resource for the token's team. Resources of another team answer 404 as well, never 403.
- `429`: The plan rate limit is exhausted for one window.

### Exemple de requête (cURL)

```bash
curl -X DELETE "https://api.nessflow.com/v1/webhooks/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57" \
  -H "Authorization: Bearer $NESSFLOW_TOKEN"
```

## Renouveler le secret de signature

`POST /v1/webhooks/{webhook}/rotate-secret`

Émet un nouveau secret de signature, rendu cette seule fois. Pendant 24 heures, chaque envoi porte une signature `v1` par secret : le destinataire bascule sans perdre d’événement.

- Scopes requis: `webhooks:write`
- Coût en débit: 1 unité(s)

### Paramètres

- `webhook` (path, string (uuid), requis): The webhook identifier.

### Réponses

- `200`: OK.
- `400`: Malformed request, such as an invalid pagination cursor (`pagination.invalid_cursor`).
- `401`: Missing, invalid or expired token, or a member who left the team.
- `403`: The plan does not include the API (`api.not_in_plan`) or this verb
(`api.write_not_in_plan`), or the token lacks a scope
(`token.missing_scope`).

- `404`: No such resource for the token's team. Resources of another team answer 404 as well, never 403.
- `429`: The plan rate limit is exhausted for one window.

### Exemple de requête (cURL)

```bash
curl -X POST "https://api.nessflow.com/v1/webhooks/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/rotate-secret" \
  -H "Authorization: Bearer $NESSFLOW_TOKEN"
```

### Exemple de réponse

```json
{
    "data": {
        "id": "0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57",
        "url": "https://exemple.fr/",
        "description": "string",
        "events": [
            "crawl.completed"
        ],
        "active": true,
        "disabled_reason": "consecutive_failures",
        "disabled_at": "2026-10-01T09:30:00Z",
        "consecutive_failures": 42,
        "last_delivery_at": "2026-10-01T09:30:00Z",
        "secret_rotation_ends_at": "2026-10-01T09:30:00Z",
        "created_at": "2026-10-01T09:30:00Z",
        "secret": "string"
    }
}
```

## Envoyer un événement de test

`POST /v1/webhooks/{webhook}/test`

Met en file un événement `webhook.test` signé (`202`), même vers un abonnement désactivé, pour vérifier une correction avant de le réactiver. Il n’est jamais relancé et ne compte jamais comme un échec. Son résultat se lit dans le journal des livraisons.

- Scopes requis: `webhooks:write`
- Coût en débit: 1 unité(s)
- Idempotency-Key acceptée

### Paramètres

- `webhook` (path, string (uuid), requis): The webhook identifier.

### Réponses

- `202`: OK.
- `400`: Malformed request, such as an invalid pagination cursor (`pagination.invalid_cursor`).
- `401`: Missing, invalid or expired token, or a member who left the team.
- `403`: The plan does not include the API (`api.not_in_plan`) or this verb
(`api.write_not_in_plan`), or the token lacks a scope
(`token.missing_scope`).

- `404`: No such resource for the token's team. Resources of another team answer 404 as well, never 403.
- `429`: The plan rate limit is exhausted for one window.

### Exemple de requête (cURL)

```bash
curl -X POST "https://api.nessflow.com/v1/webhooks/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/test" \
  -H "Authorization: Bearer $NESSFLOW_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)"
```

### Exemple de réponse

```json
{
    "data": {
        "id": "0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57",
        "event_id": "0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57",
        "event_type": "string",
        "status": "pending",
        "attempts": 42,
        "response_status": 200,
        "error_reason": "refused_address",
        "duration_ms": 42,
        "next_attempt_at": "2026-10-01T09:30:00Z",
        "delivered_at": "2026-10-01T09:30:00Z",
        "created_at": "2026-10-01T09:30:00Z"
    }
}
```

## Lister les livraisons

`GET /v1/webhooks/{webhook}/deliveries`

Le journal des livraisons, les plus récentes d’abord, conservé 30 jours. Seuls le statut, la durée et un motif stable sont conservés, jamais le corps de la réponse du destinataire.

- Scopes requis: `webhooks:read`
- Coût en débit: 1 unité(s)

### Paramètres

- `webhook` (path, string (uuid), requis): The webhook identifier.
- `limit` (query, integer, facultatif): Items per page, 1 to 100 (default 25).
- `cursor` (query, string, facultatif): Opaque cursor from `meta.next_cursor`. It is bound to the request that issued it; reusing it with other filters returns `400 pagination.invalid_cursor`.

### Réponses

- `200`: OK.
- `401`: Missing, invalid or expired token, or a member who left the team.
- `403`: The plan does not include the API (`api.not_in_plan`) or this verb
(`api.write_not_in_plan`), or the token lacks a scope
(`token.missing_scope`).

- `404`: No such resource for the token's team. Resources of another team answer 404 as well, never 403.
- `422`: Invalid query or body values (`validation_failed`), with `errors` by field.
- `429`: The plan rate limit is exhausted for one window.

### Exemple de requête (cURL)

```bash
curl "https://api.nessflow.com/v1/webhooks/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/deliveries" \
  -H "Authorization: Bearer $NESSFLOW_TOKEN"
```

### Exemple de réponse

```json
{
    "data": [
        {
            "id": "0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57",
            "event_id": "0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57",
            "event_type": "string",
            "status": "pending",
            "attempts": 42,
            "response_status": 200,
            "error_reason": "refused_address",
            "duration_ms": 42,
            "next_attempt_at": "2026-10-01T09:30:00Z",
            "delivered_at": "2026-10-01T09:30:00Z",
            "created_at": "2026-10-01T09:30:00Z"
        }
    ],
    "meta": {
        "limit": 100,
        "has_more": true,
        "next_cursor": "string"
    }
}
```
