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
-
200OK. -
401Missing, invalid or expired token, or a member who left the team. -
403The 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`). -
429The 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
-
urlstring · requisAn HTTPS URL that resolves to a public address.
-
eventsstring[] · requisValeurs (crawl.completed, crawl.failed, crawl.stopped, export.ready, recommendations.ready, signal.detected)
-
descriptionstring | null · facultatif
Réponses
-
201OK. -
400Malformed request, such as an invalid pagination cursor (`pagination.invalid_cursor`). -
401Missing, invalid or expired token, or a member who left the team. -
403The 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`). -
422Invalid query or body values (`validation_failed`), with `errors` by field. -
429The 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
-
webhookpath · string (uuid) · requisThe webhook identifier.
Réponses
-
200OK. -
401Missing, invalid or expired token, or a member who left the team. -
403The 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`). -
404No such resource for the token's team. Resources of another team answer 404 as well, never 403. -
429The 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
-
webhookpath · string (uuid) · requisThe webhook identifier.
Corps de la requête
-
urlstring · facultatif -
eventsstring[] · facultatifValeurs (crawl.completed, crawl.failed, crawl.stopped, export.ready, recommendations.ready, signal.detected)
-
descriptionstring | null · facultatif -
activeboolean · facultatif
Réponses
-
200OK. -
401Missing, invalid or expired token, or a member who left the team. -
403The 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`). -
404No such resource for the token's team. Resources of another team answer 404 as well, never 403. -
422Invalid query or body values (`validation_failed`), with `errors` by field. -
429The 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
-
webhookpath · string (uuid) · requisThe webhook identifier.
Réponses
-
204Deleted. -
401Missing, invalid or expired token, or a member who left the team. -
403The 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`). -
404No such resource for the token's team. Resources of another team answer 404 as well, never 403. -
429The 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
-
webhookpath · string (uuid) · requisThe webhook identifier.
Réponses
-
200OK. -
400Malformed request, such as an invalid pagination cursor (`pagination.invalid_cursor`). -
401Missing, invalid or expired token, or a member who left the team. -
403The 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`). -
404No such resource for the token's team. Resources of another team answer 404 as well, never 403. -
429The 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
-
webhookpath · string (uuid) · requisThe webhook identifier.
Réponses
-
202OK. -
400Malformed request, such as an invalid pagination cursor (`pagination.invalid_cursor`). -
401Missing, invalid or expired token, or a member who left the team. -
403The 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`). -
404No such resource for the token's team. Resources of another team answer 404 as well, never 403. -
429The 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
-
webhookpath · string (uuid) · requisThe webhook identifier.
-
limitquery · integer · facultatifItems per page, 1 to 100 (default 25).
-
cursorquery · string · facultatifOpaque 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
-
200OK. -
401Missing, invalid or expired token, or a member who left the team. -
403The 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`). -
404No such resource for the token's team. Resources of another team answer 404 as well, never 403. -
422Invalid query or body values (`validation_failed`), with `errors` by field. -
429The 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"
}
}