API WEBFUL v1
API REST pour lire vos donnees analytics WEBFUL depuis n'importe quelle application, script ou automation (n8n, Make, dashboards custom). RGPD, propre, versionnee.
https://webful.fr/api/v1
GET only
JSON
Playground live
Voir chaque endpoint en action
Page de demonstration construite uniquement avec l'API publique, sur le compte demo@webful.fr (lecture seule). Chaque stat affiche le JSON brut et un exemple de code.
Introduction
L'API WEBFUL v1 permet de consulter toutes les donnees visibles dans votre dashboard, programmes dans vos scripts, workflows n8n/Make, ou interfaces internes. Lecture seule, couverture complete du dashboard (compte, sites, statistiques, performance, SEO, geolocalisation, funnels, webhooks, vue agency).
Aucune mutation (creation, modification, suppression) n'est exposee en v1 : l'API est lecture seule. Les operations d'administration restent dans le dashboard web.
Ne jamais embarquer une cle dans un frontend public.
CORS est ouvert (Access-Control-Allow-Origin: *) pour permettre les dashboards internes
et proxies serveur, mais toute cle exposee dans un JS accessible aux visiteurs serait
immediatement captee. Appelez l'API depuis votre backend.
Conventions
- Format : JSON (UTF-8). Seul
GETest expose en v1. - Dates et horodatages : toutes les dates completes sont en
UTC au format ISO 8601
(
YYYY-MM-DDTHH:MM:SSZ, ex:2026-04-22T15:30:00Z). Les dates seules (ex:current_period_end) restent au formatYYYY-MM-DD. - Request ID : chaque reponse inclut un header
X-Request-Id(etmeta.request_id/error.request_id). Transmettez-le quand vous signalez un probleme. - Version : chaque reponse inclut
X-API-Version: v1. - Pagination :
page(>=1) etper_page(1-200). La reponse contientpage,per_page,totalettotal_pages. Sipagedepassetotal_pages, la liste retournee est vide. - Compression : gzip supporte en production. Envoyez
Accept-Encoding: gzippour l'activer. Les environnements de developpement locaux peuvent ne pas le negocier selon leur config Apache. - Parametres inconnus : les query parameters non documentes pour un endpoint
sont silencieusement ignores. L'API ne renvoie
BAD_REQUESTque pour les parametres documentes dont la valeur sort de l'enum ou des bornes. - Headers CORS exposes : les clients front peuvent lire
X-Request-Id,X-API-Version,X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-ResetetX-Data-Freshness(listes dansAccess-Control-Expose-Headers). - Fraicheur des donnees : pour les endpoints qui dependent d'un batch
(PageSpeed, analyseur SEO), la reponse inclut le header
X-Data-Freshnessavec le timestamp ISO 8601 UTC de la donnee la plus recente disponible (ex:X-Data-Freshness: 2026-04-22T03:14:00Z). Header absent = donnees temps reel (insertion a chaque hit ou agregation a la requete). Concerne actuellement/sites/{id}/performanceet/sites/{id}/seo. - Champs
metatransverses : chaque reponse inclutmeta.generated_at(ISO 8601 UTC),meta.api_version("v1"),meta.request_idetmeta.rate_limit. Les endpoints qui acceptentfrom/toreflechissent les bornes dansmeta.period. Les endpoints qui acceptent un parametre filtre (granularity,view,category,by,type,limit,max_depth,country_code, etc.) le reflechissent dansmeta(valeur effective appliquee). - Fraicheur des donnees : les endpoints dependants d'un batch (PageSpeed, SEO)
exposent deux champs
meta:meta.data_source(ex:"pagespeed_insights") etmeta.refresh_cadence(enum ouverte, valeurs observees :"daily","on_demand"). Le headerX-Data-Freshnessest en plus renvoye pour/performance(timestamp ISO du dernier batch).
Quickstart
- Connectez-vous au dashboard > Profil.
- Dans la section API Developpeurs, generez une cle API.
- Copiez la cle (affichee une seule fois, format
wbf_live_...). - Testez immediatement avec
curl:
# Tester votre cle (doit retourner les infos du compte) curl -H "Authorization: Bearer wbf_live_xxxxxxxxxxxxxxxxxxxx" \ https://webful.fr/api/v1/account
Exemples dans d'autres langages : JavaScript / Python / PHP.
Exemples multi-langages
Les blocs ci-dessous montrent comment appeler GET /account
dans chaque langage. Le pattern est identique pour tous les endpoints : Bearer token dans le header
Authorization, JSON en retour. Substituez simplement le path et les query params.
Ne jamais embarquer la cle dans un frontend public. Appelez l'API depuis votre backend ou une fonction serverless (Cloudflare Worker, Vercel, Netlify).
cURL
curl -H "Authorization: Bearer $WEBFUL_KEY" \ -H "Accept: application/json" \ "https://webful.fr/api/v1/account"
JavaScript (fetch, Node 18+ / navigateur)
const res = await fetch('https://webful.fr/api/v1/account', { headers: { 'Authorization': `Bearer ${process.env.WEBFUL_KEY}`, 'Accept': 'application/json', }, }); if (!res.ok) { const err = await res.json(); throw new Error(`${err.error.code}: ${err.error.message}`); } const { data, meta } = await res.json(); console.log(data.plan, data.subscription_status); // Rate limit : res.headers.get('X-RateLimit-Remaining')
Python (requests)
import os, requests headers = { "Authorization": f"Bearer {os.environ['WEBFUL_KEY']}", "Accept": "application/json", } res = requests.get("https://webful.fr/api/v1/account", headers=headers, timeout=10) if res.status_code != 200: err = res.json()["error"] raise RuntimeError(f"{err['code']}: {err['message']} (request_id={err['request_id']})") payload = res.json() print(payload["data"]["plan"], payload["data"]["subscription_status"]) # Rate limit : res.headers["X-RateLimit-Remaining"]
PHP (cURL)
<?php $ch = curl_init('https://webful.fr/api/v1/account'); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . getenv('WEBFUL_KEY'), 'Accept: application/json', ], CURLOPT_TIMEOUT => 10, ]); $body = curl_exec($ch); $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $json = json_decode($body, true); if ($status !== 200) { throw new RuntimeException( $json['error']['code'] . ': ' . $json['error']['message'] ); } echo $json['data']['plan'], "\n";
Avec query parameters
Exemple : GET /sites/WBF-12345/pages?from=2026-04-01&to=2026-04-30&sort=visits&per_page=100
# JavaScript const qs = new URLSearchParams({ from: '2026-04-01', to: '2026-04-30', sort: 'visits', per_page: '100', }); await fetch(`https://webful.fr/api/v1/sites/WBF-12345/pages?${qs}`, { headers }); # Python params = {"from": "2026-04-01", "to": "2026-04-30", "sort": "visits", "per_page": 100} requests.get(url, headers=headers, params=params)
Integrations no-code
- n8n / Make / Zapier : utilisez un node "HTTP Request" GET,
ajoutez le header
Authorization: Bearer {{cle}}, mode d'auth "None" (l'auth est gerée par le header custom). Le JSON de reponse est directement parsé par le node suivant. - Postman / Insomnia / Bruno : importez le schema OpenAPI 3.0 pour generer automatiquement une collection avec les 18 endpoints et leurs parametres.
- Google Sheets / Excel : via Apps Script (
UrlFetchApp.fetch) ou Power Query. Stockez la cle dans les Script Properties (Sheets) ou un coffre-fort (Excel).
Authentification
Toutes les requetes authentifiees utilisent un Bearer token dans le header
Authorization. La cle est liee a votre compte et donne
acces a l'integralite des donnees du compte (tous les sites, tous les endpoints).
Authorization: Bearer wbf_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxx Accept: application/json
Pour revoquer une cle, regenerez-la ou utilisez le bouton Revoquer dans le dashboard. L'ancienne cle cesse immediatement de fonctionner.
Scopes granulaires (cle read-only, limitee a certains sites, multiples cles dev/staging/prod) : non disponibles en v1, prevus en v2.
Mode sandbox : non disponible en v1.
Pour tester sans impacter vos donnees reelles, creez un compte dedie ou un site de test.
Un vrai bac a sable (jeu de donnees fictif et cles wbf_test_)
est prevu pour la v2.
Rate limiting
Le quota est compte par cle API (pas par compte, pas par IP) sur une fenetre glissante d'une heure. Chaque nouvelle requete "libere" la plus ancienne qui sort de la fenetre ; il n'y a pas de reset brutal.
| Plan | Limite | Fenetre |
|---|---|---|
| Standard | 1 000 req | 1 heure glissante |
| Agence | 5 000 req | 1 heure glissante |
Chaque reponse inclut les headers :
X-RateLimit-Limit: limite horaire applicable a la cleX-RateLimit-Remaining: requetes restantes dans la fenetre couranteX-RateLimit-Reset: timestamp Unix (secondes) auquel le plus vieux hit sortira de la fenetre (indicatif, la fenetre est glissante)
Depassement : HTTP 429 avec code RATE_LIMITED et header Retry-After.
Erreurs
Les erreurs renvoient un JSON standardise. La liste des code est fermee
(UPPER_SNAKE_CASE) : toute nouvelle valeur sera documentee avant introduction.
Les champs code, message, documentation_url et request_id
sont toujours presents. Le champ details est optionnel :
il est fourni pour BAD_REQUEST (objet decrivant le parametre fautif) et parfois pour
RATE_LIMITED (quota/fenetre). Ne dependez pas de sa presence systematique.
| HTTP | Code | Signification | Cause probable |
|---|---|---|---|
| 400 | BAD_REQUEST | Parametre invalide ou manquant | Valeur hors enum, format de date incorrect, per_page hors bornes. |
| 401 | UNAUTHORIZED | Authentification echouee | Header Authorization manquant, cle invalide ou revoquee, format de cle incorrect. |
| 403 | FORBIDDEN | Acces refuse | Email non verifie, compte suspendu, ressource appartenant a un autre compte. |
| 404 | NOT_FOUND | Ressource ou endpoint inconnu | ID de site invalide, URL d'endpoint inexistante. |
| 429 | RATE_LIMITED | Quota horaire depasse | Voir header Retry-After et X-RateLimit-Reset. |
| 500 | INTERNAL_ERROR | Erreur serveur | Bug cote WEBFUL. Transmettez le request_id au support. |
Enumerations
Les champs suivants sont des enumerations fermees : toute valeur retournee est garantie appartenir a la liste. Les ajouts se feront de facon retro-compatible (annoncee dans le changelog, jamais en silence).
| Champ | Valeurs |
|---|---|
| account.plan | free | solo | pro | agency | agency_plus | enterprise |
| account.subscription_status | active | trialing | past_due | canceled | incomplete | inactive |
| site.plan | free | premiumLe plan attache a un site (distinct du plan du compte). free = quota 1 000 visites/mois, premium = 100 000. |
| site.integration_type | wordpress | html | shopify | prestashop | wix | squarespace |
| site.report_frequency | none | daily | weekly | monthly |
| site.report_mode | traffic_only | traffic_plus_perf |
| status.activity | ok | warning | error |
| status.online | ok | warning | error | na |
| status.plugin | ok | warning | pending |
| status.perf | ok | warning | error | na |
| status.js_errors | ok | error | na |
| alert.severity | info | low | medium | high | critical |
| alert.code |
no_visit_ever, no_visit_recent, site_offline,
plugin_pending, plugin_outdated, perf_warning,
perf_critical, js_errors, traffic_drop
Selon le code, l'objet
alert peut contenir des champs contextuels en plus de code et severity :
|
| metrics.trend_reliability | full | partial | none |
| stats.granularity (param) | hour | day | month |
| pages.sort (param) | visits | unique_visitors | avg_time |
| referrers.category | direct | search | social | ai | referral | internalLe parametre category accepte en plus la valeur all. |
| events.view (param) | aggregate | stream |
| conversions.view (param) | aggregate | stream |
| conversions.conversion_type | tel_click | email_click | form_submit | page_visit | outbound_click | file_downloadLe parametre type accepte en plus la valeur all. |
| performance.device (param) | mobile | desktop | both |
| performance.report.status | excellent | good | average | poor |
| seo.view (param) | list | report |
| geolocation.by (param) | country | region | city |
| funnel.step.type | pageview | conversion | event |
| funnel.period (param) | 1 | 7 | 30 | 90 | 365 (jours, fenetre glissante) |
| webhook_delivery.status | pending | success | failed | retryingLe parametre status accepte en plus la valeur all. |
| webhook.event_type |
traffic.alert | health.critical | health.recovered | conversion.new | error.js_recurring
threshold_value (integer, nullable) n'est utilise que pour error.js_recurring
(seuil d'erreurs JS declenchant le webhook). null pour tous les autres types.
|
Endpoints disponibles
GET /api/v1/account
Demo Informations du compte associe a la cle API (id, email, plan, statut abonnement).
Fraicheur : temps reel.
Exemple :
# Ping : verifie la cle et retourne les infos du compte curl -H "Authorization: Bearer $WEBFUL_KEY" \ https://webful.fr/api/v1/account
Reponse :
{
"data": {
"id": 42,
"email": "alice@example.com",
"plan": "pro",
"subscription_status": "active",
"current_period_end": "2026-05-15" // date-only, null si abonnement sans echeance connue (free / canceled)
},
"meta": {
"generated_at": "2026-04-22T15:30:00Z",
"api_version": "v1",
"request_id": "01KPV9CC3MA9641GSF1CW7SBKA",
"rate_limit": { "limit": 1000, "remaining": 987, "reset": 1776888389 }
}
}
GET /api/v1/sites
Demo Liste paginee des sites appartenant au compte.
Tri : created_at DESC (le plus recent en premier).
Fraicheur : temps reel.
Parametres :
page(int >= 1, defaut1)per_page(int, min1, defaut50, max200)
curl -H "Authorization: Bearer $WEBFUL_KEY" \ "https://webful.fr/api/v1/sites?per_page=20"
Reponse (extrait) :
{
"data": [
{
"site_id": "WBF-12345",
"name": "Mon site",
"url": "https://example.com",
"aliases": ["www.example.com"],
"thumbnail_url": "https://webful.fr/uploads/thumbnails/WBF-12345.webp?v=1768234689",
"plan": "premium",
"integration_type": "wordpress",
"plugin_version": "2.5.3", // null si integration_type != "wordpress" ou plugin pas encore installe
"is_plugin_outdated": false, // bool ou null (voir note)
"tracking_verified": true, // opt-in manuel via le dashboard (voir note)
"tracking_active": true, // dérivé : first_visit_at != null
"report_frequency": "weekly",
"report_mode": "traffic_plus_perf",
"created_at": "2026-01-15T09:22:10Z"
}
],
"pagination": {
"page": 1, "per_page": 20, "total": 4, "total_pages": 1
},
"meta": {
"latest_plugin_version": "2.5.3", // reference pour is_plugin_outdated
"request_id": "01KPV9CG8XD1G6D5EXDXMTKJJ2"
/* generated_at, api_version, rate_limit */
}
}
Champs derives :
is_plugin_outdated = true si plugin_version < latest_plugin_version
(semver), false sinon, null si la notion n'est pas applicable
(integration_type != "wordpress", ou plugin pas encore installe).
tracking_active = true si au moins une visite a ete enregistree
(first_visit_at != null). Repond a la question "le tracker fonctionne-t-il ?".
Distinct de tracking_verified, qui est un opt-in manuel : l'utilisateur a
clique sur "Verifier mon tracking" dans le dashboard et WEBFUL a fetch le HTML pour
confirmer la presence du snippet. Un site peut avoir tracking_active: true
et tracking_verified: false (tracker en place mais verification manuelle
jamais declenchee). C'est normal.
GET /api/v1/sites/{site_id}
Demo Detail complet d'un site : metadonnees, plan, quota du mois en cours, dernier controle de sante, dernier snapshot performance et SEO.
Fraicheur : metadonnees et quota = temps reel ;
health.* = dernier health-check (~30 min) ;
performance et seo = dernier batch d'analyse (quotidien).
Parametres de chemin :
site_id(formatWBF-XXXXX) : obligatoire. Doit appartenir au compte de la cle (sinonNOT_FOUND).
curl -H "Authorization: Bearer $WEBFUL_KEY" \
https://webful.fr/api/v1/sites/WBF-12345
Reponse :
{
"data": {
"site_id": "WBF-12345",
"name": "Mon site",
"url": "https://example.com",
"aliases": ["www.example.com"],
"thumbnail_url": "https://webful.fr/uploads/thumbnails/WBF-12345.webp?v=1768234689",
"plan": "premium",
"integration_type": "wordpress",
"plugin_version": "2.5.3", // null si integration_type != "wordpress" ou plugin pas encore installe
"is_plugin_outdated": false, // bool ou null (voir note sous /sites)
"tracking_verified": true, // opt-in manuel via le dashboard
"tracking_verified_at": "2025-10-21T13:56:21Z",
"tracking_active": true, // derive : first_visit_at != null (= "le tracker voit des visites")
"report_frequency": "weekly",
"report_mode": "traffic_plus_perf",
"created_at": "2025-10-21T13:32:09Z",
"first_visit_at": "2025-10-21T13:56:21Z",
"last_visit_at": "2026-04-22T10:45:00Z",
"quota": {
"visits_this_month": 1846,
"monthly_limit": 100000, // null si plan sans limite connue
"window": "calendar_month"
},
"health": {
"is_online": true,
"http_status": 200,
"response_time_ms": 289,
"checked_at": "2026-04-22T14:43:38Z"
}, // null si aucun health-check
"performance": {
"score": 65,
"device": "mobile",
"analyzed_at": "2026-04-20T23:01:23Z"
}, // null si jamais analyse
"seo": {
"score": 82,
"analyzed_at": "2026-04-01T16:41:53Z"
} // null si jamais analyse
},
"meta": {
"generated_at": "2026-04-22T19:50:17Z",
"api_version": "v1",
"request_id": "01KPVBWPXG0KG0FXV12D7Q5AAH",
"site_id": "WBF-12345",
"rate_limit": { "limit": 1000, "remaining": 992, "reset": 1776891106 }
}
}
quota.monthly_limit est null si le plan
du site n'a pas de limite documentee en v1 (ex: ancien plan custom). Dans ce cas, le champ
visits_this_month reste valide comme simple compteur.
Les blocs health, performance et
seo sont null tant qu'aucun run n'a eu lieu. Ne supposez jamais leur presence ;
testez !== null avant d'acceder aux sous-champs.
GET /api/v1/sites/{site_id}/stats
Demo Totaux et serie temporelle agregee pour un site sur une periode. Le filtre anti-bot (sessions avec ≥ 3 IPs distinctes) est applique automatiquement.
Fraicheur : temps reel (base tracker ecrite en direct).
Parametres :
from(YYYY-MM-DD, defautJ-30)to(YYYY-MM-DD, defaut aujourd'hui). Fenetre max : 365 jours.granularity(enum fermehour | day | month, defautday). Valeur hors liste =BAD_REQUEST.
La granularite hour fait apparaitre le bucket
au format ISO 8601 UTC (2026-04-22T14:00:00Z). day utilise
YYYY-MM-DD, month utilise YYYY-MM. Les buckets sans trafic
sont absents de la serie (pas remplis a zero).
curl -H "Authorization: Bearer $WEBFUL_KEY" \ "https://webful.fr/api/v1/sites/WBF-12345/stats?from=2026-04-01&to=2026-04-22&granularity=day"
Reponse (extrait) :
{
"data": {
"totals": {
"page_views": 2237,
"unique_visitors": 1049,
"sessions": 1484,
"bounces": 1170,
"bounce_rate": 78.8, // % arrondi 1 decimale
"avg_time_on_page": 1576, // secondes, cappe a 1800 par visite (0 si aucune donnee)
"conversions": 498 // table conversions, filtre anti-bot applique
},
"timeseries": [
{ "bucket": "2026-04-01", "page_views": 43, "unique_visitors": 22, "sessions": 25 },
{ "bucket": "2026-04-02", "page_views": 48, "unique_visitors": 29, "sessions": 34 }
/* ... tri : bucket ASC */
]
},
"meta": {
"generated_at": "2026-04-22T19:50:40Z",
"api_version": "v1",
"request_id": "01KPVBX9PY43Z49SJ7CKX7SSMF",
"site_id": "WBF-12345",
"period": { "from": "2026-04-01", "to": "2026-04-22" },
"granularity": "day",
"rate_limit": { "limit": 1000, "remaining": 991, "reset": 1776891106 }
}
}
GET /api/v1/sites/{site_id}/pages
Demo Top pages consultees sur la periode, agregees par url.
Filtre anti-bot applique.
Fraicheur : temps reel.
Parametres :
from,to(YYYY-MM-DD ; defaut J-30 / aujourd'hui ; max 365 j)page,per_page(pagination, max 200)sort(enum fermevisits | unique_visitors | avg_time, defautvisits)
Tri : critere demande DESC, puis url ASC
pour etre deterministe. avg_time ignore les visites a temps nul (champ vide ou 0)
et est plafonne a 1800 s (30 min) par visite pour neutraliser les onglets oublies
(standard GA/Matomo). Les durees reelles superieures sont donc comptees comme 1800.
curl -H "Authorization: Bearer $WEBFUL_KEY" \ "https://webful.fr/api/v1/sites/WBF-12345/pages?per_page=10&sort=visits"
Reponse (extrait) :
{
"data": [
{
"url": "https://example.com/",
"title": "Exemple - Accueil", // peut etre null
"visits": 554,
"unique_visitors": 358,
"avg_time": 1107 // secondes, cappe a 1800 par visite (0 si inconnu)
}
],
"pagination": { "page": 1, "per_page": 10, "total": 87, "total_pages": 9 },
"meta": {
"request_id": "01KPVBX9R7RPNJ7E5YA4VC0V71",
"site_id": "WBF-12345",
"period": { "from": "2026-03-23", "to": "2026-04-22" },
"sort": "visits",
"rate_limit": { "limit": 1000, "remaining": 990, "reset": 1776891106 }
/* generated_at, api_version egalement presents */
}
}
GET /api/v1/sites/{site_id}/referrers
Demo Sources de trafic agregees par host normalise
(host en lowercase, sans www.) et categorisees.
Les referrers vides ou invalides sont regroupes sous (direct).
Fraicheur : temps reel. Filtre anti-bot applique.
Parametres :
from,to(YYYY-MM-DD ; defaut J-30 / aujourd'hui ; max 365 j)page,per_page(pagination, max 200)category(enum fermeall | direct | search | social | ai | referral | internal, defautall). Filtre sur la categorie retournee.
Categorisation :
search (google, bing, yahoo, ddg, yandex, baidu, ecosia, qwant),
social (facebook, x/twitter, linkedin, instagram, pinterest, reddit, tiktok, youtube, whatsapp),
ai (chatgpt, claude, perplexity, gemini, copilot, you.com),
internal (host du site lui-meme),
direct (pas de referrer),
referral (tout le reste). Tri : visits DESC, puis host ASC.
curl -H "Authorization: Bearer $WEBFUL_KEY" \ "https://webful.fr/api/v1/sites/WBF-12345/referrers?category=search"
Reponse (extrait) :
{
"data": [
{ "host": "google.com", "category": "search", "visits": 429 },
{ "host": "bing.com", "category": "search", "visits": 47 }
],
"pagination": { "page": 1, "per_page": 50, "total": 5, "total_pages": 1 },
"meta": {
"request_id": "01KPVBZ962S72CWF1M6W99002W",
"site_id": "WBF-12345",
"period": { "from": "2026-03-23", "to": "2026-04-22" },
"category": "search",
"rate_limit": { "limit": 1000, "remaining": 989, "reset": 1776891106 }
}
}
GET /api/v1/sites/{site_id}/events
Demo Evenements custom trackes (tracker webful.track('nom', {...})).
Deux vues : aggregee par event_name, ou flux chronologique brut.
Fraicheur : temps reel.
Filtre anti-bot applique (meme logique que /stats, /pages, /conversions).
Parametres :
from,to(YYYY-MM-DD ; defaut J-30 / aujourd'hui ; max 365 j)view(enum fermeaggregate | stream, defautaggregate)event_name(string libre, max 100 caracteres, match strict). Non une enum fermee : les integrateurs utilisent leurs propres noms. Compatible avecview=aggregateETview=stream.page,per_page(pagination, max 200) — pertinente uniquement enview=stream. Enview=aggregate, le nombre de rows est borne par le nombre d'event_namedistincts (rarement > 50) ; paginer n'a pas de sens.
Tri :
aggregate = count DESC, puis event_name ASC.
stream = timestamp DESC (le plus recent en premier).
# Vue aggregee : tous les event_name curl -H "Authorization: Bearer $WEBFUL_KEY" \ "https://webful.fr/api/v1/sites/WBF-12345/events" # Vue aggregee, filtree sur un nom d'evenement (renvoie une seule row) curl -H "Authorization: Bearer $WEBFUL_KEY" \ "https://webful.fr/api/v1/sites/WBF-12345/events?event_name=signup" # Flux chronologique, filtre sur un nom d'evenement curl -H "Authorization: Bearer $WEBFUL_KEY" \ "https://webful.fr/api/v1/sites/WBF-12345/events?view=stream&event_name=signup&per_page=50"
Reponse view=aggregate :
{
"data": [
{
"event_name": "signup",
"count": 142,
"unique_sessions": 128,
"first_seen": "2026-03-23T12:04:18Z",
"last_seen": "2026-04-22T18:32:09Z"
}
],
"pagination": { "page": 1, "per_page": 50, "total": 8, "total_pages": 1 },
"meta": {
"site_id": "WBF-12345",
"view": "aggregate",
"event_name": null, // filtre actif si non null
"period": { "from": "2026-03-23", "to": "2026-04-22" }
/* request_id, generated_at, api_version, rate_limit */
}
}
Reponse view=stream :
{
"data": [
{
"id": 98412,
"event_name": "signup",
"data": { "plan": "pro", "source": "pricing" }, // JSON decode si possible, sinon string brute, sinon null
"session_id": "WBF-1776784067916-v9p4anhjc",
"timestamp": "2026-04-22T18:32:09Z"
}
],
"pagination": { "page": 1, "per_page": 50, "total": 1284, "total_pages": 26 }
}
GET /api/v1/sites/{site_id}/conversions
Demo Definition : une conversion est un evenement
explicitement marque comme tel par le tracker (table conversions, distincte des
evenements custom /events et des visites /stats). Les types
sont une enum fermee de 6 valeurs.
Fraicheur : temps reel.
Filtre anti-bot applique : les session_id dont les page_views
presentent ≥ 3 ip_hash distincts sur la fenetre sont exclues, comme pour
/stats, /pages, /referrers. Le ratio
conversions/sessions est donc coherent entre tous les endpoints.
Parametres :
from,to(YYYY-MM-DD ; defaut J-30 / aujourd'hui ; max 365 j)type(enum fermeall | tel_click | email_click | form_submit | page_visit | outbound_click | file_download, defautall)view(enum fermeaggregate | stream, defautaggregate)page,per_page(pagination, max 200 ; utilise seulement enview=stream)
Tri :
aggregate = count DESC puis conversion_type ASC ;
stream = timestamp DESC, puis id DESC.
curl -H "Authorization: Bearer $WEBFUL_KEY" \ "https://webful.fr/api/v1/sites/WBF-12345/conversions?view=aggregate"
Reponse view=aggregate :
{
"data": [
{
"conversion_type": "page_visit",
"count": 265,
"unique_sessions": 159,
"first_seen": "2026-03-23T14:06:20Z",
"last_seen": "2026-04-21T13:10:02Z"
},
{
"conversion_type": "form_submit",
"count": 178,
"unique_sessions": 72,
"first_seen": "2026-03-25T23:24:05Z",
"last_seen": "2026-04-19T09:15:04Z"
}
],
"meta": {
"site_id": "WBF-12345",
"period": { "from": "2026-03-23", "to": "2026-04-22" },
"view": "aggregate",
"type": "all",
"total_conversions": 498, // voir note sous l'exemple
"rate_limit": { "limit": 1000, "remaining": 987, "reset": 1776891106 }
}
}
meta.total_conversions : nombre total
de conversions sur la periode (apres filtre anti-bot), respectant le parametre
type. Avec type=all, c'est la somme de toutes les
rows.count de la vue aggregate. Avec un filtre (type=form_submit),
c'est le total pour ce type uniquement. Champ present dans les deux vues
(aggregate et stream).
Reponse view=stream :
{
"data": [
{
"id": 74218,
"conversion_type": "form_submit",
"label": "contact-form", // libelle attache par le tracker, peut etre null
"page_url": "https://example.com/contact",
"referrer": "https://google.com/", // peut etre null
"device_type": "mobile", // peut etre null, valeurs : mobile | desktop | tablet
"session_id": "WBF-1776784067916-v9p4anhjc",
"visitor_id": "WBF-1776784068139-o9dkc0wfy", // format identique a session_id (cf note ci-dessous)
"timestamp": "2026-04-19T09:15:04Z"
}
],
"pagination": { "page": 1, "per_page": 50, "total": 498, "total_pages": 10 }
}
Le champ visitor_id est un identifiant pseudonyme
non-trackant : il ne permet pas de re-identifier un individu hors du site concerne. Ne le stockez pas
comme cle utilisateur longue duree.
Note contrat : WEBFUL ne fait
pas de re-identification cross-session. En pratique, visitor_id
coincide donc actuellement avec session_id (1 visite = 1 identifiant). Les deux
champs restent distincts pour ne pas casser l'integration si un mode opt-in visiteur persistant
est ajoute plus tard. Ne presumez pas d'unicite visitor_id vs session_id
dans vos calculs.
GET /api/v1/agency/overview
Demo Snapshot de la vue agency : pour chaque site, voyants de sante, metriques (visites, tendance, scores perf/SEO), alertes et score de sante global.
Fraicheur : visites en temps reel ;
perf_score / seo_score = dernier batch d'analyse (quotidien) ;
voyant online = check sante toutes les 30 minutes.
Parametres :
period(enum ferme1 | 7 | 30 | 90, defaut30) : fenetre de comparaison en jours. Valeur hors liste =BAD_REQUEST.
Pourquoi une enum fermee ici,
alors que /sites/{id}/stats accepte from/to libres ?
/agency/overview est une vue snapshot synthetique (voyants, alertes, score de
sante global), pas un endpoint de stats libres. Les 4 valeurs 1 | 7 | 30 | 90
correspondent aux fenetres pour lesquelles la fiabilite de trend_percent est
calibree (trend_reliability) et dont le calcul "periode vs periode precedente"
reste performant meme avec beaucoup de sites. Pour des plages libres, utilisez
/sites/{id}/stats (jusqu'a 365 jours via from/to).
trend_percent
Compare les visites de la periode courante a la periode precedente
de meme duree (ex: period=7 compare les 7 derniers jours aux 7 jours d'avant).
Valeurs : -100 a +inf, arrondi a 1 decimale. 0 si aucun trafic
sur la periode precedente, 100 si nouveau trafic.
Le champ trend_reliability (full | partial | none)
indique si la comparaison est statistiquement significative.
curl -H "Authorization: Bearer $WEBFUL_KEY" \ "https://webful.fr/api/v1/agency/overview?period=7"
Reponse (extrait) :
{
"data": {
"summary": {
"total_sites": 4,
"total_visits": 12840,
"period_days": 7,
"avg_trend": 12.3,
"avg_perf_score": 87,
"total_alerts": 2
},
"sites": [
{
"site_id": "WBF-12345",
"name": "Mon site",
"url": "https://example.com",
"thumbnail_url": "https://webful.fr/uploads/thumbnails/WBF-12345.webp?v=1768234689",
"integration_type": "wordpress",
"plugin_version": "2.5.3", // null si integration_type != "wordpress" ou plugin pas encore installe
"is_plugin_outdated": false, // bool ou null (voir note sous /sites)
"tracking_active": true, // derive : au moins une visite enregistree
"report_frequency": "weekly",
"report_mode": "traffic_plus_perf",
"created_at": "2026-01-15T09:22:10Z",
"status": {
"activity": "ok", // voir section Enumerations
"online": "ok",
"plugin": "ok",
"perf": "warning",
"js_errors": "ok"
},
"metrics": {
"visits": 3210,
"visits_previous": 2787,
"trend_percent": 15.2,
"trend_reliability": "full",
"perf_score": 64,
"seo_score": 82,
"last_visit": "2026-04-22T10:45:00Z",
"days_since_visit": 0,
"js_errors_24h": 0
},
"health_score": 80,
"alerts": [ // voir section Enumerations : code + severity + champs contextuels variables
{ "code": "perf_warning", "severity": "low", "score": 64 },
{ "code": "traffic_drop", "severity": "medium", "percent": -39.3 }
]
}
]
},
"meta": {
"generated_at": "2026-04-22T15:30:00Z",
"api_version": "v1",
"request_id": "01KPV9CMHTV7W7915410A1CS91",
"period": { "days": 7, "label": "7d" },
"latest_plugin_version": "2.5.3",
"rate_limit": { "limit": 5000, "remaining": 4987, "reset": 1776888389 }
}
}
meta.period reprend le parametre effectivement utilise
(apres validation) ; meta.latest_plugin_version = derniere version stable du plugin
WordPress WEBFUL, sert de reference pour le voyant status.plugin.
Les sites renvoyes incluent les memes champs que
/api/v1/sites (url, thumbnail_url, integration_type, etc.)
pour eviter un second appel N+1.
status.plugin pour les sites non-WordPress
(integration HTML, Shopify, etc.) : ok des que du trafic est detecte, pending tant
qu'aucune visite n'a ete enregistree. Pas de na ni warning dans ce cas.
GET /api/v1/account/usage
Demo Consommation du quota mensuel du compte : vues utilisees ce mois, plafond du plan, pourcentage, breakdown par site.
Fraicheur : temps reel (mise a jour a chaque hit trackable).
Fenetre : mois calendaire UTC (YYYY-MM). Remise a zero au 1er de chaque mois a 00:00 UTC.
Parametres :
Aucun.
curl -H "Authorization: Bearer $WEBFUL_KEY" \ "https://webful.fr/api/v1/account/usage"
Reponse :
{
"data": {
"period": "2026-04",
"views_used": 12840,
"views_limit": 100000, // null si plan illimite
"views_remaining": 87160, // null si illimite
"percentage": 12.84, // null si illimite
"unlimited": false,
"per_site": [
{ "site_id": "WBF-12345", "views": 8210 },
{ "site_id": "WBF-67890", "views": 4630 }
],
"resets_at": "2026-05-01T00:00:00Z",
"api_requests_last_24h": 272
},
"meta": {
"plan": "agency",
"rate_limit": { "limit": 5000, "remaining": 4999, "reset": 1776944400 }
}
}
per_site[].views est garanti
coherent avec quota.visits_this_month renvoye par /sites/{id} : meme source,
meme fenetre (mois calendaire UTC), meme filtre anti-bot.
api_requests_last_24h : nombre de requetes API faites par
ce compte sur les 24 dernieres heures glissantes, toutes cles API
cumulees, tous status HTTP confondus (2xx, 4xx, 5xx). A ne pas confondre avec
meta.rate_limit qui est scope par cle sur une fenetre d'1h.
GET /api/v1/sites/{site_id}/performance
Demo Scores PageSpeed Insights du dernier batch + historique jusqu'a 90 jours.
Source : table performance_analyses, alimentee par un CRON quotidien (~03:00 UTC).
Fraicheur : batch quotidien.
Un site pas encore analyse renvoie latest = null (ou latest.{mobile|desktop} = null
en mode device=both), jamais des 0. Header X-Data-Freshness renvoye avec
l'horodatage ISO du dernier batch disponible.
Parametres :
device(enum fermemobile | desktop | both, defautmobile)history_days(int 0-90, defaut 30) : 0 = pas d'historique retourne (seulementlatest)
Tri historique : analyzed_at ASC (du plus ancien au plus recent).
curl -H "Authorization: Bearer $WEBFUL_KEY" \ "https://webful.fr/api/v1/sites/WBF-12345/performance?device=mobile&history_days=30"
Reponse :
{
"data": {
"latest": {
"analyzed_at": "2026-04-22T03:12:44Z",
"device_type": "mobile",
"scores": {
"performance": 92, // 0-100 ou null
"accessibility": 89,
"seo": 95,
"best_practices": 100
},
"metrics": {
"lcp_seconds": 1.06,
"fid_ms": 16,
"cls": 0,
"fcp_seconds": 1.06,
"ttfb_seconds": 0.2,
"speed_index_seconds": 1.83,
"tbt_ms": 0
},
"report": {
"status": "excellent", // voir Enumerations : excellent | good | average | poor
"message": "Bravo ! Votre site est parfaitement optimise."
}
},
"history": [
{ "analyzed_at": "2026-03-23T03:12:44Z", "device_type": "mobile",
"performance": 88, "accessibility": 89, "seo": 95, "best_practices": 100,
"lcp_seconds": 1.4, "cls": 0, "tbt_ms": 20 }
]
},
"meta": {
"site_id": "WBF-12345",
"device": "mobile",
"history_days": 30,
"data_source": "pagespeed_insights",
"refresh_cadence": "daily"
}
}
Reponse (device=both, extrait) :
{
"data": {
"latest": {
"mobile": { "analyzed_at": "2026-04-22T03:12:44Z",
"scores": { "performance": 92, ... },
"metrics": { ... }, "report": { ... } },
"desktop": { "analyzed_at": "2026-04-22T03:14:02Z",
"scores": { "performance": 98, ... },
"metrics": { ... }, "report": { ... } }
// Si l'un des deux devices n'a pas encore ete analyse : null a la place de l'objet.
},
"history": [
{ "analyzed_at": "2026-04-21T03:11:02Z", "device_type": "mobile", "performance": 90, ... },
{ "analyzed_at": "2026-04-21T03:11:48Z", "device_type": "desktop", "performance": 97, ... }
// Les entrees mobile et desktop sont melees, triees par analyzed_at ASC.
// Filtrer cote client sur device_type si un seul graphe par device est souhaite.
]
},
"meta": { "site_id": "WBF-12345", "device": "both", "history_days": 30,
"data_source": "pagespeed_insights", "refresh_cadence": "daily" }
}
En mode device=both, data.latest
devient un objet {"mobile": ..., "desktop": ...}. Si un seul device a ete analyse,
la cle manquante vaut null (et non un objet vide). L'historique retourne les analyses
des deux devices meles, chacune portant son propre device_type.
GET /api/v1/sites/{site_id}/seo
Demo Analyses SEO stockees : liste des URLs analysees (view=list)
ou rapport detaille pour une URL precise (view=report).
Fraicheur : batch a la demande (declenche manuellement depuis l'outil SEO du dashboard). Les URLs jamais analysees n'apparaissent pas.
Parametres :
view(enum fermelist | report, defautlist)url(string, obligatoire siview=report) : URL exacte analysee (max 500 caracteres)page,per_page(pagination, max 200 ; utilise seulement enview=list)
Tri (view=list) : analyzed_at DESC, puis id DESC.
curl -H "Authorization: Bearer $WEBFUL_KEY" \ "https://webful.fr/api/v1/sites/WBF-12345/seo?view=list"
Reponse view=list :
{
"data": [
{
"url": "https://example.com/",
"score": 88,
"score_previous": 82, // nullable : null a la 1re analyse
"score_delta": 6, // nullable : null a la 1re analyse
"analyzed_at": "2026-04-15T10:22:00Z"
}
],
"pagination": { "page": 1, "per_page": 50, "total": 12, "total_pages": 1 },
"meta": {
"site_id": "WBF-12345",
"view": "list",
"data_source": "webful_seo_analyzer",
"refresh_cadence": "on_demand" // relance manuelle depuis le dashboard
}
}
Reponse view=report :
{
"data": {
"url": "https://example.com/",
"score": 88,
"score_previous": 82, // nullable
"score_delta": 6, // nullable
"analyzed_at": "2026-04-15T10:22:00Z",
"results": [
{ "rule_id": "title_exists", "passed": true, "message": "Balise title presente",
"value": "Accueil - Example" }, // string
{ "rule_id": "h1_count", "passed": true, "message": "1 balise H1",
"value": 1 }, // integer
{ "rule_id": "text_html_ratio", "passed": true, "message": "Ratio texte/HTML correct",
"value": 0.27 }, // float
{ "rule_id": "mobile_viewport", "passed": true, "message": "Viewport mobile present",
"value": true }, // boolean
{ "rule_id": "broken_links", "passed": false, "message": "3 lien(s) casse(s)",
"value": { // object (structure propre au rule_id)
"total_links": 42,
"broken_count": 3,
"broken_links": ["/old-page", "https://dead.example.com"]
} },
{ "rule_id": "meta_description", "passed": false, "message": "Meta description absente",
"value": null } // null
]
},
"meta": {
"site_id": "WBF-12345",
"view": "report",
"data_source": "webful_seo_analyzer",
"refresh_cadence": "on_demand"
}
}
Les rule_id sont stables entre versions et servent
de cles pour mapper vers vos propres libelles. Liste exhaustive via le dashboard (section SEO).
results[].value est un type variable
dependant du rule_id et du fait que la regle passe ou echoue :
- string : regles qui renvoient un contenu textuel
(
title_exists,h1_unique,meta_description). - integer : regles de comptage
(
title_length,word_count,external_scripts,og_tags). - float : ratios et scores
(
text_html_ratio,page_speed). - boolean : presence/absence d'un element HTML
(
heading_structure,https,mobile_viewport,canonical). - object : details d'echec pour certaines regles, structure propre a chaque
rule_id. Exemples observes :broken_links→{total_links, broken_count, broken_links: [url, ...]};images_alt→{total, without_alt, missing_alt_images: [url, ...]}. Une meme regle peut renvoyer unintegerquand elle passe et unobjectquand elle echoue (ex:broken_links). - null : regle qui a echoue avant d'avoir pu mesurer.
Coder defensivement cote client :
tester typeof value (ou Array.isArray / is_array) avant
d'appeler .toString(), une methode de formatage ou un template. La liste ci-dessus
est indicative : de nouvelles regles peuvent s'ajouter en version mineure, et le type de
value pour une regle donnee peut etre etendu (ex: integer → object) en version
mineure aussi, tant que le type reste parseable de maniere defensive.
Chaque regle appartient a une categorie (critical | important | optimization)
qui determine sa penalite ; la categorie n'est pas retournee cote API pour l'instant.
GET /api/v1/sites/{site_id}/geolocation
Demo Repartition geographique du trafic : pays, regions ou villes des visiteurs.
Fraicheur : temps reel. La resolution d'une IP est differee de quelques minutes (job async), donc les visites tres recentes peuvent encore etre non-geolocalisees et exclues du total. Filtre anti-bot applique.
Parametres :
from,to(YYYY-MM-DD ; defaut J-30 / aujourd'hui ; max 365 j)by(enum fermecountry | region | city, defautcountry)country_code(ISO 3166-1 alpha-2, ex:FR,US) : filtre pour obtenir les regions/villes d'un pays donnepage,per_page(max 200)
Tri : views DESC, puis nom ASC (determinisme).
curl -H "Authorization: Bearer $WEBFUL_KEY" \ "https://webful.fr/api/v1/sites/WBF-12345/geolocation?by=city&country_code=FR"
Reponse (by=city, country_code=FR) :
{
"data": [
{
"country_code": "FR",
"country_name": "France",
"region_name": "Paris Department",
"city_name": "Paris",
"views": 1448,
"unique_visitors": 61,
"sessions": 248,
"percentage": 70.77
}
],
"meta": {
"site_id": "WBF-12345",
"period": { "from": "2026-03-23", "to": "2026-04-22" },
"by": "city",
"country_code": "FR", // string | null : filtre effectivement applique
"total_views": 2046
},
"pagination": { "page": 1, "per_page": 50, "total": 4, "total_pages": 1 }
}
En by=country, les champs renvoyes sont
country_code, country_name + metriques. En by=region,
s'ajoute region_name. En by=city, s'ajoutent region_name
et city_name. percentage = part du total (apres filtres) attribuee a ce groupe.
meta.country_code reflete le filtre applique (null si non specifie).
GET /api/v1/sites/{site_id}/user-flows
Demo Parcours utilisateurs : pages d'entree, pages de sortie, top sequences de pages visitees, et stats d'engagement.
Fraicheur : temps reel (agregation directe sur page_views).
Filtre anti-bot applique.
Parametres :
from,to(YYYY-MM-DD ; defaut J-7 / aujourd'hui ; max 365 j)limit(int 1-50, defaut 10) : taille des top listes (entry_pages,exit_pages,top_paths)max_depth(int 2-10, defaut 10) : profondeur maximale d'un parcours retenu danstop_paths
Tri :
entry_pages / exit_pages : count DESC, puis url ASC.
top_paths : occurrences DESC (paths vus moins de 2 fois exclus).
curl -H "Authorization: Bearer $WEBFUL_KEY" \ "https://webful.fr/api/v1/sites/WBF-12345/user-flows?limit=5"
Reponse :
{
"data": {
"stats": {
"total_sessions": 101,
"avg_pages_per_session": 5.09,
"bounce_rate": 65.35,
"bounces": 66
},
"entry_pages": [
{ "url": "https://example.com/", "count": 20, "percentage": 19.8 }
],
"exit_pages": [
{ "url": "https://example.com/contact", "count": 14, "percentage": 13.86 }
],
"top_paths": [
{
"pages": ["https://example.com/", "https://example.com/pricing"],
"depth": 2,
"occurrences": 12,
"percentage": 11.88
}
]
},
"meta": {
"site_id": "WBF-12345",
"period": { "from": "2026-04-15", "to": "2026-04-22" },
"limit": 5, // valeur effective du parametre limit
"max_depth": 10 // valeur effective du parametre max_depth
}
}
GET /api/v1/sites/{site_id}/funnels
Demo Liste des entonnoirs de conversion configures pour le site. Pour les donnees de conversion d'un funnel precis, utiliser l'endpoint de detail ci-dessous.
Fraicheur : temps reel (configuration en BDD).
Parametres :
page,per_page(max 200)
Tri : created_at DESC.
curl -H "Authorization: Bearer $WEBFUL_KEY" \ "https://webful.fr/api/v1/sites/WBF-12345/funnels"
Reponse :
{
"data": [
{
"id": 1,
"name": "Lead direct",
"steps": [
{ "order": 1, "type": "pageview", "value": "https://example.com", "label": "Accueil" },
{ "order": 2, "type": "pageview", "value": "https://example.com/contact", "label": "Contact" },
{ "order": 3, "type": "conversion", "value": "form_submit", "label": "Formulaire envoye" }
],
"created_at": "2026-03-29T18:58:29Z",
"updated_at": "2026-03-29T18:58:29Z"
}
],
"pagination": { "page": 1, "per_page": 50, "total": 1, "total_pages": 1 }
}
GET /api/v1/sites/{site_id}/funnels/{funnel_id}
Donnees de conversion d'un funnel : visiteurs uniques a chaque etape, dropoff, taux de conversion.
Fraicheur : temps reel. Fenetre : glissante depuis maintenant.
Parametres :
period(enum ferme1 | 7 | 30 | 90 | 365jours, defaut30)
curl -H "Authorization: Bearer $WEBFUL_KEY" \ "https://webful.fr/api/v1/sites/WBF-12345/funnels/1?period=30"
Reponse :
{
"data": {
"id": 1,
"name": "Lead direct",
"total_entered": 400,
"total_converted": 47,
"conversion_rate": 11.75,
"steps": [
{ "order": 1, "type": "pageview", "value": "https://example.com",
"label": "Accueil", "visitors": 400, "dropoff": 0,
"rate_from_previous": 100, "rate_from_first": 100 },
{ "order": 2, "type": "pageview", "value": "https://example.com/contact",
"label": "Contact", "visitors": 72, "dropoff": 328,
"rate_from_previous": 18, "rate_from_first": 18 },
{ "order": 3, "type": "conversion", "value": "form_submit",
"label": "Formulaire envoye", "visitors": 47, "dropoff": 25,
"rate_from_previous": 65.28, "rate_from_first": 11.75 }
]
},
"meta": { "site_id": "WBF-12345", "funnel_id": 1, "period_days": 30, "window": "rolling" }
}
step.type = enum fermee
pageview | conversion | event (voir Enumerations). Le calcul dedoublonne par
ip_hash : un meme visiteur qui complete le funnel dans plusieurs sessions ne compte qu'une fois.
step.label : peut etre null dans /funnels (liste) si le libelle
n'a pas ete saisi en configuration. Dans /funnels/{id} (detail), label
prend value comme fallback pour garantir un affichage non-vide cote client.
meta.period_days (int) reflete le parametre period applique ;
meta.window vaut "rolling" (fenetre glissante depuis maintenant).
Un funnel sans steps (configuration incomplete) renvoie steps: []
avec total_entered: 0, total_converted: 0, conversion_rate: 0.
GET /api/v1/sites/{site_id}/webhooks
Demo Liste des webhooks sortants configures pour ce site (URL cible, evenements souscrits, etat de la derniere livraison).
Fraicheur : temps reel.
Securite : le secret HMAC du webhook n'est jamais
expose par l'API (consultation/regeneration via le dashboard uniquement).
Parametres :
page,per_page(max 200)
Tri : created_at DESC.
curl -H "Authorization: Bearer $WEBFUL_KEY" \ "https://webful.fr/api/v1/sites/WBF-12345/webhooks"
Reponse :
{
"data": [
{
"id": 17,
"name": "Slack - Conversions",
"url": "https://hooks.slack.com/services/...",
"is_active": true,
"events": [
{ "event_type": "conversion.new", "threshold_value": null },
{ "event_type": "error.js_recurring", "threshold_value": 5 }
],
"last_delivery": { // null si aucune livraison encore
"event_type": "conversion.new",
"status": "success", // voir Enumerations
"response_code": 200, // nullable (echec reseau)
"created_at": "2026-04-22T14:30:00Z",
"delivered_at": "2026-04-22T14:30:00Z"
},
"created_at": "2026-03-12T10:00:00Z",
"updated_at": "2026-04-01T08:15:00Z"
}
],
"pagination": { "page": 1, "per_page": 50, "total": 1, "total_pages": 1 }
}
Types d'evenements (enum fermee,
voir Enumerations) : traffic.alert, health.critical,
health.recovered, conversion.new, error.js_recurring.
threshold_value (integer, nullable) : utilise uniquement pour
error.js_recurring (nombre minimum d'erreurs JS recurrentes declenchant le webhook).
null pour tous les autres types d'evenements.
last_delivery est null si le webhook n'a jamais ete declenche.
response_code est null en cas d'echec reseau (timeout, DNS, TLS)
avant reception d'une reponse HTTP.
GET /api/v1/sites/{site_id}/webhooks/{webhook_id}/deliveries
Historique des livraisons (payload envoye, code HTTP recu, status). Le cycle de retry est : tentative initiale, puis jusqu'a 5 retries avec backoff exponentiel avant abandon.
Fraicheur : temps reel.
Parametres :
status(enum fermeall | pending | success | failed | retrying, defautall)page,per_page(max 200)
Tri : created_at DESC, puis id DESC.
curl -H "Authorization: Bearer $WEBFUL_KEY" \ "https://webful.fr/api/v1/sites/WBF-12345/webhooks/17/deliveries?status=failed"
Reponse :
{
"data": [
{
"id": 4521,
"delivery_id": "d-3c7b1ae2-4f80-4e8a-9c3e-2b1d8e0a1234",
"event_type": "conversion.new",
"status": "failed",
"attempt": 5,
"response_code": 500, // nullable (null en echec reseau)
"response_body": "Internal Server Error", // nullable, tronque a 2000 caracteres
"payload": { "conversion_type": "form_submit", "site_id": "WBF-12345" },
"created_at": "2026-04-22T10:00:00Z",
"delivered_at": null, // null tant que non delivre avec succes
"next_retry_at": null // null si succes ou abandon
}
],
"pagination": { "page": 1, "per_page": 50, "total": 12, "total_pages": 1 },
"meta": {
"site_id": "WBF-12345",
"webhook": { "id": 17, "name": "Slack - Conversions", "url": "https://...", "is_active": true },
"status": "failed"
}
}
delivery_id : identifiant stable de la livraison,
format d-<uuid-v4>. Inclus en header X-Webful-Delivery-Id du payload envoye
au client, utilisable pour correler une livraison recue cote receveur avec cette ligne d'historique.
attempt : numero de tentative (1 = initiale, 2-5 = retries avec backoff exponentiel).
next_retry_at est null si la livraison a abouti (status=success)
ou si tous les retries sont epuises (status=failed avec attempt=5).
Schema OpenAPI 3.0
Un schema OpenAPI 3.0.3 decrit formellement tous les endpoints, parametres, enums et formes de reponse. Il est servi en YAML sur :
https://webful.fr/api/v1/openapi.yaml
Utilisations typiques :
- Generation de SDK :
openapi-generator-cli generate -i https://webful.fr/api/v1/openapi.yaml -g python -o ./webful-client(remplacezpythonpartypescript-fetch,go,php, etc.) - Import dans Postman / Insomnia / Bruno : collection complete generee automatiquement.
- Import dans n8n / Make : les connecteurs HTTP recents acceptent l'URL du schema pour pre-remplir les appels.
- Rendu interactif : collez l'URL dans editor.swagger.io ou redocly.github.io/redoc pour une doc navigable avec "Try it out".
CORS ouvert sur le schema (header
Access-Control-Allow-Origin: *) : les outils cote navigateur peuvent le recuperer directement.
Pas d'authentification requise.
Le schema est maintenu a la main en parallele de cette page. Source de verite : cette documentation ; toute divergence observee est un bug a signaler.
Versioning & breaking changes
- Versionning dans l'URL : les endpoints sont prefixes par
/api/v1/. Une v2 vivra en parallele de la v1, pas en remplacement. - Changements retro-compatibles (nouveau champ optionnel, nouvelle valeur ajoutee a une enum, nouvel endpoint) : livres sans preavis, documentes dans le changelog. Un integrateur defensif doit tolerer les champs inconnus.
- Breaking changes (suppression d'un champ, changement de type, restriction d'une enum) : uniquement via une nouvelle version majeure. La version precedente reste maintenue 6 mois minimum apres annonce.
- Canal d'annonce des deprecations :
- Email aux proprietaires de cles API actives (adresse du compte)
- Changelog public sur cette page (section "Changelog" en bas)
- Header
Deprecation(RFC 8594) etSunsetretournes sur les endpoints/champs concernes a partir de l'annonce
- Header
X-API-Version: renvoye a chaque reponse (ex:v1). Utile pour diagnostiquer en cas de proxy qui reecrit les URLs.
Changelog
v1.0 — 23 avril 2026
lancementVersion initiale de l'API publique WEBFUL. 18 endpoints en lecture seule couvrant l'integralite du dashboard : compte, sites, statistiques (stats, pages, referrers, events, conversions), performance, SEO, geolocalisation, parcours utilisateurs, funnels, webhooks, vue agency multi-sites.
Authentification Bearer par cle API compte-level,
rate limiting par cle (1 000 req/h standard, 5 000 req/h agence), dates ISO 8601 UTC,
request_id trace sur chaque reponse, enumerations fermees documentees,
pagination standardisee, filtres anti-bot appliques sur les endpoints de trafic,
X-Data-Freshness sur les endpoints batch (performance, SEO).
Outillage : schema OpenAPI 3.0.3 servi en ligne (CORS ouvert), exemples d'integration en cURL, JavaScript, Python, PHP et no-code (n8n, Make, Postman, Google Sheets).
Les evolutions futures (ajouts retro-compatibles et breaking changes via v2) seront journalisees ici et annoncees par email aux proprietaires de cles actives.
Roadmap
La v1 expose en lecture l'integralite du dashboard. Les chantiers suivants sont a l'etude pour les prochaines versions :
- Rendu interactif de la doc (Swagger UI / Redoc) alimenté par le schema OpenAPI deja en ligne.
- CORS configurable par compte : whitelist d'origines depuis le dashboard
pour les usages frontaux maitrises (au-dela du
*par defaut). - Scopes granulaires (cles read-only, restreintes a un sous-ensemble de sites, cles dev/staging/prod) — necessite une refonte de la table cles, prevu en v2.
- Mode sandbox avec jeu de donnees fictif et cles
wbf_test_— v2. - Mutations (POST/PUT/DELETE) pour gestion programmatique des sites, funnels, webhooks — v2.
- SDKs officiels (JavaScript, Python, PHP) — v2.
Un retour a nous partager ? Ecrivez-nous depuis la page contact.