Statistiques (alpha)

Une ligne dans vos pages donne leurs visites, sans cookie, et un bouton d'avis si vous en voulez un. Statistiques est en alpha, dans l'app web seulement.

Brancher un site

Dans l'app web, ouvrez Statistiques, ajoutez le domaine, et collez la ligne donnée dans le <head> de chaque page. Les sous-domaines comptent pour leur domaine. Les pages en localhost ne sont pas comptées.

html
<script defer src="https://zenhook.dev/z.js" data-site="zs_your_site_key"></script>

Pour qu'un agent IA s'en charge, utilisez Copier les instructions pour votre IA dans l'installation du site : la ligne, les événements et la vérification, écrits pour l'agent.

Avec un token seul, un agent fait tout : cet appel ajoute le site et renvoie sa clé, puis il écrit la ligne, les événements et l'objectif ci-dessous.

bash
curl -X POST -H "Authorization: Bearer $ZENHOOK_TOKEN" -H "Content-Type: application/json" \
  -d '{"domain":"example.com"}' "https://zenhook.dev/api/sites"

Événements

Les pages vues s'enregistrent sans code, navigation d'une app monopage comprise. Enregistrez les actions qui comptent là où elles ont lieu :

javascript
window.zenhook?.track("signup", { plan: "pro" })

Objectifs

Un objectif, c'est ce qui compte comme une réussite sur le site : un événement qu'il envoie (signup) ou une page atteinte (/merci, ou /blog/* pour tout ce qui est dessous). Avec un objectif, la page du site montre ses conversions et son taux, et chaque liste gagne une colonne de conversion : vous voyez quelle source, quel pays ou quel appareil amène les gens qui le font. Un objectif se compte sur les visites déjà enregistrées, donc son passé est là dès qu'il est ajouté. Dans l'app, la page du site propose des objectifs d'après ce que le site envoie déjà ; un agent en ajoute un en un appel, avec le domaine ou l'identifiant du site :

bash
curl -X POST -H "Authorization: Bearer $ZENHOOK_TOKEN" -H "Content-Type: application/json" \
  -d '{"kind":"event","match":"signup"}' "https://zenhook.dev/api/sites/example.com/goals"

Un GET sur la même adresse liste les objectifs et des suggestions tirées des 30 derniers jours du site ; DELETE /api/sites/example.com/goals/:id arrête d'en compter un. 10 objectifs par site au plus. Ajoutez ensuite goal=:id à la requête des chiffres : totals gagne conversions, et chaque ligne les siennes.

Avis

Activez Avis pour le site, puis demandez là où ça compte, quand ça compte. Depuis votre propre interface, un appel envoie la réponse : votre design, votre moment, rien de nous sur la page. Chaque réponse arrive comme un événement New feedback dans votre canal Feedback, avec la réaction, la page, l'appareil et l'email s'il a été laissé, et vous notifie comme tout événement.

javascript
// A reaction in one tap is enough; a message and an email are optional.
const { ok, error } = await window.zenhook.sendFeedback({
  reaction: "up",
  message: "The export took three clicks too many.",
  email: "[email protected]",
});

Le script n'est pas obligatoire : la même requête s'écrit à la main.

javascript
// The same request without the script, from a page of your site only.
await fetch("https://zenhook.dev/api/f", {
  method: "POST",
  body: JSON.stringify({ site: "zs_your_site_key", reaction: "up", message: "...", url: location.href }),
});

Un serveur (le serveur d'une app, un bot, un serveur de jeu) n'a pas de page : il poste sur l'adresse de webhook de votre canal Feedback, comme tout événement Zenhook. L'adresse est dans les réglages du canal. Avec ce titre et votre domaine comme source, il se range dans les avis du site, à côté de ce qu'envoient vos pages :

bash
curl -X POST "$ZENHOOK_FEEDBACK_URL" -H "Content-Type: application/json" \
  -d '{"title":"New feedback","message":"The export took three clicks too many.","source":"example.com","actor":{"email":"[email protected]"},"fields":[{"label":"Reaction","value":"down"}]}'

Le contrat, depuis une page. reaction est la réponse en un clic, "up" ou "down", affichée en pouce au début de sa ligne ; message fait 4 000 caractères au plus, et l'un des deux suffit ; email est facultatif ; url est l'adresse complète de la page. La réponse est 200 avec { "ok": true }, ou { "error": "..." } qui dit la cause : 400 un champ est faux ou manquant, 403 les avis sont coupés pour le site ou la requête ne vient pas d'une de ses pages, 404 aucun site n'a cette clé, 429 trop de messages. Pour tester, envoyez depuis une page sur localhost : il arrive dans votre canal Feedback marqué comme un test, et répond { "ok": true, "test": true }.

Ce qui le protège. La clé du site dans une page est publique par conception, ce n'est donc pas elle qui vous protège. L'origine du navigateur doit être le domaine du site, ce qu'un autre site ne peut pas falsifier dans le navigateur de ses visiteurs. Un script hors navigateur le peut, donc les quantités sont bornées : une adresse envoie 10 messages en 10 minutes et 20 par jour, un site en reçoit 200 par jour, et un flux qui ne s'arrête pas arrive sur votre téléphone en un résumé par heure. L'adresse de webhook du canal est un secret : gardez-la sur votre serveur, dans ZENHOOK_FEEDBACK_URL.

Notre bouton, c'est un mot de plus sur la ligne : data-feedback le pose en bas à droite. Tout élément portant data-zenhook-feedback, ou window.zenhook.feedback(), ouvre le même panneau sans le bouton. Le panneau prend la langue de la page (son html lang ; data-lang sur le script en fixe une autre) et son thème ; data-theme="light" ou "dark" en impose un :

html
<script defer src="https://zenhook.dev/z.js" data-site="zs_your_site_key" data-feedback></script>
<!-- or, from your own button, with no button of ours: -->
<button data-zenhook-feedback>Feedback</button>

Lire les chiffres

La page Statistiques montre la période d'un coup d'œil. L'Assistant répond aux questions (« quelles pages ont amené des inscriptions cette semaine ? »). Les scripts et les agents IA passent par l'API avec un token des Réglages, Tokens d'API :

bash
curl -H "Authorization: Bearer $ZENHOOK_TOKEN" \
  "https://zenhook.dev/api/sites/stats?site=example.com&period=7d&dimensions=page,referrer"
json
{
  "site": "example.com",
  "from": "2026-09-22T18:00:00.000Z",
  "to": "2026-09-29T18:00:00.000Z",
  "totals": { "visitors": 412, "pageviews": 1290, "events": 37 },
  "series": [{ "at": "2026-09-23T00:00:00.000Z", "visitors": 58, "pageviews": 170 }],
  "breakdowns": {
    "page": [{ "value": "/", "visitors": 300, "count": 520 }],
    "referrer": [{ "value": "google.com", "visitors": 120, "count": 180 }]
  }
}

site est un domaine ou l'identifiant d'un site. period vaut 24h, 7d, 30d ou 90d, ou donnez from et to. dimensions : page, referrer, country, device, browser, os, event, utm_source, utm_campaign, utm_content, campaign (source / annonce, comme meta / spring-video). Filtres : page (/tarifs, /blog/* pour tout ce qui est dessous, plusieurs séparés par des virgules, ! devant pour tout sauf : !/app/*), referrer (direct pour aucun), country, device, event, utm_source, utm_campaign, utm_content (les visiteurs arrivés par elle, avec toutes les pages qu'ils ont vues). limit : 1 à 50 lignes par liste. goal : l'identifiant d'un objectif, pour les conversions au total et par ligne. Une source, c'est là où la journée d'un visiteur sur le site a commencé : les pages suivantes ne comptent jamais comme direct. Une campagne se lit dans les balises utm_ de la page d'arrivée, ou dans l'identifiant de clic d'une annonce (Google, Microsoft, TikTok, X, LinkedIn, Reddit, Snapchat, Pinterest) quand le lien n'a pas de balise ; l'identifiant lui-même n'est jamais gardé.

Rapport PDF

La page d'un site a un bouton Rapport PDF : les chiffres de la période affichée avec leur évolution depuis la période précédente, les visiteurs par jour et chaque liste avec sa part, à envoyer à quelqu'un qui n'ouvre jamais Zenhook. Un script ou un agent obtient le même fichier avec un token, pour une période glissante ou un mois calendaire :

bash
curl -H "Authorization: Bearer $ZENHOOK_TOKEN" -o report.pdf \
  "https://zenhook.dev/api/sites/report?site=example.com&from=2026-10-01&to=2026-11-01&tz=Europe/Paris"

period (24h, 7d, 30d, 90d) ou from et to, tz pour les jours, goal=:id pour ses conversions, lang=en, fr, pt ou de (par défaut la langue du compte).

Vie privée

Aucun cookie, et rien dans le navigateur sauf si le visiteur s'y oppose. Un visiteur est une empreinte de l'adresse et du navigateur, avec un sel qui change chaque jour et n'est jamais gardé avec les données : on ne peut pas suivre une personne d'un jour à l'autre. Les robots ne sont pas comptés. Les morceaux d'adresse qui ressemblent à un token, un identifiant ou un email sont enregistrés comme :id ou :email, et la partie après le « ? » n'est jamais gardée. Les visites sont gardées 13 mois. Les navigateurs qui envoient Global Privacy Control ou Do Not Track ne sont pas comptés, et un lien avec data-zenhook-optout (ou zenhook.optOut()) arrête de compter un visiteur qui s'y oppose. L'installation du site donne le paragraphe pour sa page de confidentialité.