Aller au contenu
NessFlow
Menu
    Sommaire de la documentation

    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.

    cURL

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

    PHP

    $client = new GuzzleHttp\Client(['base_uri' => 'https://api.nessflow.com/v1/']);
    
    $response = $client->request('GET', 'webhooks', [
        'headers' => [
            'Authorization' => 'Bearer '.getenv('NESSFLOW_TOKEN'),
        ],
    ]);
    
    $data = json_decode((string) $response->getBody(), true);

    JavaScript

    const response = await fetch('https://api.nessflow.com/v1/webhooks', {
      headers: {
        Authorization: `Bearer ${process.env.NESSFLOW_TOKEN}`,
      },
    });
    
    const data = await response.json();

    Python

    import os
    
    import requests
    
    response = requests.get(
        "https://api.nessflow.com/v1/webhooks",
        headers={
            "Authorization": f"Bearer {os.environ['NESSFLOW_TOKEN']}",
        },
        timeout=30,
    )
    response.raise_for_status()
    data = response.json()

    Exemple de réponse

    {
        "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

      Valeurs (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.

    cURL

    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"}'

    PHP

    $client = new GuzzleHttp\Client(['base_uri' => 'https://api.nessflow.com/v1/']);
    
    $response = $client->request('POST', 'webhooks', [
        'headers' => [
            'Authorization' => 'Bearer '.getenv('NESSFLOW_TOKEN'),
        ],
        'json' => [
            'url' => 'https://exemple.fr/',
            'events' => [
                'crawl.completed',
            ],
            'description' => 'string',
        ],
    ]);
    
    $data = json_decode((string) $response->getBody(), true);

    JavaScript

    const response = await fetch('https://api.nessflow.com/v1/webhooks', {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.NESSFLOW_TOKEN}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
          "url": "https://exemple.fr/",
          "events": [
              "crawl.completed"
          ],
          "description": "string"
      }),
    });
    
    const data = await response.json();

    Python

    import os
    
    import requests
    
    response = requests.post(
        "https://api.nessflow.com/v1/webhooks",
        headers={
            "Authorization": f"Bearer {os.environ['NESSFLOW_TOKEN']}",
        },
        json={
            "url": "https://exemple.fr/",
            "events": [
                "crawl.completed",
            ],
            "description": "string",
        },
        timeout=30,
    )
    response.raise_for_status()
    data = response.json()

    Exemple de réponse

    {
        "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.

    cURL

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

    PHP

    $client = new GuzzleHttp\Client(['base_uri' => 'https://api.nessflow.com/v1/']);
    
    $response = $client->request('GET', 'webhooks/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57', [
        'headers' => [
            'Authorization' => 'Bearer '.getenv('NESSFLOW_TOKEN'),
        ],
    ]);
    
    $data = json_decode((string) $response->getBody(), true);

    JavaScript

    const response = await fetch('https://api.nessflow.com/v1/webhooks/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57', {
      headers: {
        Authorization: `Bearer ${process.env.NESSFLOW_TOKEN}`,
      },
    });
    
    const data = await response.json();

    Python

    import os
    
    import requests
    
    response = requests.get(
        "https://api.nessflow.com/v1/webhooks/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57",
        headers={
            "Authorization": f"Bearer {os.environ['NESSFLOW_TOKEN']}",
        },
        timeout=30,
    )
    response.raise_for_status()
    data = response.json()

    Exemple de réponse

    {
        "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

      Valeurs (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.

    cURL

    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}'

    PHP

    $client = new GuzzleHttp\Client(['base_uri' => 'https://api.nessflow.com/v1/']);
    
    $response = $client->request('PATCH', 'webhooks/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57', [
        'headers' => [
            'Authorization' => 'Bearer '.getenv('NESSFLOW_TOKEN'),
        ],
        'json' => [
            'url' => 'https://exemple.fr/',
            'events' => [
                'crawl.completed',
            ],
            'description' => 'string',
            'active' => true,
        ],
    ]);
    
    $data = json_decode((string) $response->getBody(), true);

    JavaScript

    const response = await fetch('https://api.nessflow.com/v1/webhooks/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57', {
      method: 'PATCH',
      headers: {
        Authorization: `Bearer ${process.env.NESSFLOW_TOKEN}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
          "url": "https://exemple.fr/",
          "events": [
              "crawl.completed"
          ],
          "description": "string",
          "active": true
      }),
    });
    
    const data = await response.json();

    Python

    import os
    
    import requests
    
    response = requests.patch(
        "https://api.nessflow.com/v1/webhooks/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57",
        headers={
            "Authorization": f"Bearer {os.environ['NESSFLOW_TOKEN']}",
        },
        json={
            "url": "https://exemple.fr/",
            "events": [
                "crawl.completed",
            ],
            "description": "string",
            "active": True,
        },
        timeout=30,
    )
    response.raise_for_status()
    data = response.json()

    Exemple de réponse

    {
        "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.

    cURL

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

    PHP

    $client = new GuzzleHttp\Client(['base_uri' => 'https://api.nessflow.com/v1/']);
    
    $response = $client->request('DELETE', 'webhooks/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57', [
        'headers' => [
            'Authorization' => 'Bearer '.getenv('NESSFLOW_TOKEN'),
        ],
    ]);

    JavaScript

    const response = await fetch('https://api.nessflow.com/v1/webhooks/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57', {
      method: 'DELETE',
      headers: {
        Authorization: `Bearer ${process.env.NESSFLOW_TOKEN}`,
      },
    });

    Python

    import os
    
    import requests
    
    response = requests.delete(
        "https://api.nessflow.com/v1/webhooks/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57",
        headers={
            "Authorization": f"Bearer {os.environ['NESSFLOW_TOKEN']}",
        },
        timeout=30,
    )
    response.raise_for_status()

    Exemple de réponse

    Aucun corps de réponse.

    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.

    cURL

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

    PHP

    $client = new GuzzleHttp\Client(['base_uri' => 'https://api.nessflow.com/v1/']);
    
    $response = $client->request('POST', 'webhooks/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/rotate-secret', [
        'headers' => [
            'Authorization' => 'Bearer '.getenv('NESSFLOW_TOKEN'),
        ],
    ]);
    
    $data = json_decode((string) $response->getBody(), true);

    JavaScript

    const response = await fetch('https://api.nessflow.com/v1/webhooks/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/rotate-secret', {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.NESSFLOW_TOKEN}`,
      },
    });
    
    const data = await response.json();

    Python

    import os
    
    import requests
    
    response = requests.post(
        "https://api.nessflow.com/v1/webhooks/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/rotate-secret",
        headers={
            "Authorization": f"Bearer {os.environ['NESSFLOW_TOKEN']}",
        },
        timeout=30,
    )
    response.raise_for_status()
    data = response.json()

    Exemple de réponse

    {
        "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
    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.

    cURL

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

    PHP

    $client = new GuzzleHttp\Client(['base_uri' => 'https://api.nessflow.com/v1/']);
    
    $response = $client->request('POST', 'webhooks/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/test', [
        'headers' => [
            'Authorization' => 'Bearer '.getenv('NESSFLOW_TOKEN'),
            'Idempotency-Key' => bin2hex(random_bytes(16)),
        ],
    ]);
    
    $data = json_decode((string) $response->getBody(), true);

    JavaScript

    const response = await fetch('https://api.nessflow.com/v1/webhooks/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/test', {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.NESSFLOW_TOKEN}`,
        'Idempotency-Key': crypto.randomUUID(),
      },
    });
    
    const data = await response.json();

    Python

    import os
    import uuid
    
    import requests
    
    response = requests.post(
        "https://api.nessflow.com/v1/webhooks/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/test",
        headers={
            "Authorization": f"Bearer {os.environ['NESSFLOW_TOKEN']}",
            "Idempotency-Key": str(uuid.uuid4()),
        },
        timeout=30,
    )
    response.raise_for_status()
    data = response.json()

    Exemple de réponse

    {
        "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.

    cURL

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

    PHP

    $client = new GuzzleHttp\Client(['base_uri' => 'https://api.nessflow.com/v1/']);
    
    $response = $client->request('GET', 'webhooks/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/deliveries', [
        'headers' => [
            'Authorization' => 'Bearer '.getenv('NESSFLOW_TOKEN'),
        ],
    ]);
    
    $data = json_decode((string) $response->getBody(), true);

    JavaScript

    const response = await fetch('https://api.nessflow.com/v1/webhooks/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/deliveries', {
      headers: {
        Authorization: `Bearer ${process.env.NESSFLOW_TOKEN}`,
      },
    });
    
    const data = await response.json();

    Python

    import os
    
    import requests
    
    response = requests.get(
        "https://api.nessflow.com/v1/webhooks/0190f5a2-7c4e-7b1a-9d3e-2f6b8c1a4e57/deliveries",
        headers={
            "Authorization": f"Bearer {os.environ['NESSFLOW_TOKEN']}",
        },
        timeout=30,
    )
    response.raise_for_status()
    data = response.json()

    Exemple de réponse

    {
        "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"
        }
    }

    Référence de l’API Codes d’erreur Documentation de l’API

    Cette page en Markdown