Suivi et conversions
Ce qu'un lien mesure vraiment, où trouver ces chiffres, et comment il se ferme.
L'API vous dit si un lien est toujours ouvert ; seul le Dashboard montre ses performances.
L'API n'a pas de compteurs
GET /links/{id} renvoie ce que le lien est, jamais ses performances : ni vues, ni
conversions, ni revenus.
https://api.wajub.com/links/{id}curl https://api.wajub.com/links/8kD2xQpLm \
-H "Authorization: sk.xxxxxxxxxxxxxxxx…"La réponse est l'état du lien, et c'est ce que vous interrogez pour savoir s'il encaisse toujours.
La liste fonctionne de la même façon. Elle accepte per_page, search, archived pour voir les
liens archivés plutôt que les liens actifs, only_public, type (product, event,
fundraising ou donation), et sort_by (created_at, updated_at, title ou type) avec
sort_dir (desc ou asc). La liste suit le tri en premier, puis du plus récent au plus ancien
entre liens à égalité. Pour les exports, un cursor parcourt la liste dans son ordre par défaut, du
plus récent au plus ancien : avec un autre tri, la liste est paginée par numéro et ne porte pas de
curseur. Un type, un sort_by ou un sort_dir hors de ces valeurs renvoie une 422 sur ce
champ, comme un per_page hors limites.
https://api.wajub.com/linksCe qui est réellement mesuré
Chaque visite qui passe les verrous d'accès est comptée, avec sa provenance (le referrer HTTP), le pays du visiteur et son type d'appareil.
Les visiteurs uniques sont comptés par jour : la même personne qui visite deux jours différents compte donc pour deux visiteurs uniques. Lisez ce nombre comme « uniques par jour », pas comme un nombre de personnes.
Une visite bloquée n'est pas une vue
Quand un lien est protégé par mot de passe, une tentative dont le mot de passe échoue n'est pas
comptée. Une visite sur un lien non publié, expiré ou dont le plafond de vues est atteint ne l'est
pas non plus. Le compteur n'avance que pour les visites qui ont réellement atteint la page, et c'est
ce qui fait de max_views un plafond fiable.
Les chiffres, et où les lire
Liens → un lien → Vue d'ensemble est le seul endroit où ils apparaissent. Total des vues, Total des ventes et Revenu total couvrent toute la vie du lien, et Taux de conversion rapporte ces ventes à ses visiteurs. La période choisie, les 7, 14, 30 ou 90 derniers jours, ne change que les graphiques : les barres sous chaque total et Tendances des visites et des ventes. La transaction moyenne, les vues par transaction, les principaux pays et les appareils couvrent toujours les 30 derniers jours.
Le taux de conversion se calcule par visiteur, pas par vue
Le chiffre vaut transactions ÷ unique visitors, et non transactions ÷ views. Une page ouverte
quatre fois avant le paiement compte pour un visiteur et une conversion : le taux paraît donc plus
élevé qu'avec un calcul par vue. Comparez-le à lui-même dans le temps plutôt qu'à un chiffre venant
d'un autre outil.
Attribuer un canal
Aucun paramètre d'URL ne marque une visite. Un ?src=whatsapp sur l'URL partagée n'est transmis
nulle part : il n'atteint pas le paiement, il n'est pas enregistré sur la vue, et il n'apparaîtra
dans aucun rapport.
Vous disposez à la place de deux choses qui fonctionnent.
Le referrer est capturé automatiquement : le trafic qui en porte un, depuis un lien dans une publication ou une page, affiche déjà son origine dans les statistiques. Le trafic venant de WhatsApp, d'un SMS ou d'un QR code n'a pas de referrer, et c'est en pratique l'essentiel du trafic.
Donc, pour tout ce que vous devez vraiment attribuer, créez un lien par canal. Même produit, même
prix, custom_url différente : wajub.link/cafe-whatsapp, wajub.link/cafe-instagram. Chacun garde
ses propres vues, son propre taux de conversion et son propre chiffre d'affaires, et la comparaison
est exacte plutôt que déduite.
Un paiement fait sur un lien le désigne : son champ link est l'id du lien, dans l'API et dans
les webhooks payment.*, si bien que votre handler sait de quel lien, donc de quel canal, vient
l'argent. GET /payments n'a pas de filtre par lien : pour lister les paiements d'un lien, lisez
Liens → un lien → Transactions dans le Dashboard, ou gardez le link de chaque paiement à son
arrivée.
Comment un lien se ferme
Quatre réglages mettent fin à la vie d'un lien, et ils ne le ferment pas de la même façon.
| Réglage | Ferme | Le payeur obtient |
|---|---|---|
expiry_date | La page, au moment que vous choisissez | 410, avant tout affichage |
is_archived | La page, immédiatement | 410, avant tout affichage |
sales_limit | Le paiement, pas la page | 422 au moment de payer, This payment link has reached its sales limit. |
single_use_link | La page, après son premier paiement réussi | Une page qui indique que le lien a déjà servi à payer |
La différence compte. Un lien expiré ou archivé ne s'ouvre jamais : le payeur voit une page fermée et tout s'arrête là. Un lien qui a atteint son plafond de ventes s'ouvre toujours normalement, affiche le produit et le bouton, et ne refuse qu'au moment du paiement. Si vous vendez une quantité fixe, attendez-vous à ce que des payeurs tombent sur ce message plutôt que sur une page fermée.
Un lien à usage unique accepte un seul payeur à la fois. Pendant que quelqu'un paie par ce lien, un
autre payeur apprend qu'un paiement par ce lien est en cours, et que le lien rouvre si ce paiement
n'aboutit pas. Une fois un paiement réussi, le lien reste fermé, même si vous le remboursez. Aucun
champ du lien n'indique qu'il est fermé : payment.succeeded, dont le link est l'id du lien,
est votre signal. Si un second paiement aboutit malgré tout par ce lien, comme une tentative
antérieure que l'opérateur confirme en retard, le propriétaire, les administrateurs et les membres
finance de votre équipe reçoivent un e-mail qui nomme le paiement à rembourser : Wajub ne le
rembourse pas de lui-même.
link.expired est envoyé une fois, dans la journée qui suit le passage de l'expiry_date d'un
lien, sauf si le lien est archivé entre-temps, avec le lien dans data. L'archivage envoie
link.updated, tout comme une vente ou un remboursement qui change le stock d'un produit.
Atteindre le plafond de ventes et fermer un lien à usage unique n'envoient aucun événement propre.
L'archivage est l'option réversible : remettez is_archived à false via PUT /links/{id} et le
lien encaisse de nouveau, en conservant tout ce qu'il a déjà collecté. expiry_date est une
condition plutôt qu'un état : la repousser rouvre un lien qui s'était fermé à cause d'elle.
La dépublication avec is_public: false ferme aussi un lien, mais répond 404 au lieu de 410,
car un lien non publié doit donner l'impression de n'avoir jamais existé. Les verrous d'accès sont
décrits dans Personnalisation.
Ce que rend un remboursement
Un lien compte ce qu'il a gardé, pas ce qu'il a encaissé un jour. Un remboursement réussi, ou un litige que vous perdez, rend ce que le paiement avait pris :
- Un billet ou une unité dont le prix entier est remboursé ne compte plus dans
event_capacity,sales_limitni dans la quantité de son type de billet, et revient dans le stock d'un produit physique. Le remboursement d'une partie du prix d'un billet garde le billet : trois billets à 3 000 XAF remboursés de 3 000 XAF laissent deux billets vendus. - Sur une collecte ou une page de dons, le total collecté, la progression vers l'objectif et le nombre de contributeurs baissent de ce qui a été remboursé. Un don remboursé en entier quitte le mur des donateurs.
- Un paiement perdu dans un litige ne compte plus, comme un remboursement complet. Un litige encore ouvert, ou que vous avez gagné, ne change rien.
Un remboursement qui échoue, ou qui est encore en cours, ne change rien non plus.
Les webhooks d'un lien
L'onglet Webhooks d'un lien, dans le Dashboard, envoie les événements propres à ce lien à une adresse à vous, avec un secret de signature qui lui est propre. Ils s'ajoutent aux webhooks de votre compte, qui couvrent tous les paiements.
| Événement | Envoyé quand |
|---|---|
payment.success | Un paiement par le lien a réussi, quel que soit son type |
ticket_purchased | Des billets ont été achetés sur un lien d'événement |
donation.received | Un don a été reçu sur un lien de don ou de collecte |
order.placed | Une commande a été payée sur un lien de produit |
payment.refunded | Un remboursement d'un de ses paiements a réussi, une fois par remboursement, ou un litige sur l'un d'eux a été perdu |
link_expired | L'expiry_date du lien est passée |
Un paiement qui échoue n'envoie rien. Les paiements sandbox ne partent que vers les endpoints d'un
lien sandbox, avec sandbox: true. En sandbox, un remboursement ou un litige accepté depuis le
tableau de bord n'envoie pas de payment.refunded.
Chaque envoi est un POST avec un corps JSON, signé dans X-Webhook-Signature et nommé dans
X-Webhook-Event. Chaque événement porte en plus un id dans le corps, et la même valeur dans
X-Webhook-Id :
| En-tête | Ce que c'est |
|---|---|
X-Webhook-Signature | HMAC-SHA256 en hexadécimal du corps brut, avec le secret de signature de l'endpoint |
X-Webhook-Event | L'événement, comme dans le corps |
X-Webhook-Id | L'id de l'événement, comme dans le corps |
import crypto from 'node:crypto';
app.post('/webhooks/link', express.raw({ type: 'application/json' }), (req, res) => {
const expected = crypto.createHmac('sha256', process.env.LINK_WEBHOOK_SECRET).update(req.body).digest('hex');
const received = String(req.headers['x-webhook-signature'] ?? '');
if (received.length !== expected.length || !crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected))) {
return res.sendStatus(400);
}
const event = JSON.parse(req.body);
// Ignorez un event.id déjà traité, puis livrez.
res.sendStatus(200);
});Répondez par un 2xx une fois l'événement enregistré. Un envoi qui échoue est retenté, pendant 72
heures au plus, si bien qu'un même événement peut arriver plusieurs fois. Un événement garde son
id à chaque fois : gardez ceux que vous avez traités et ignorez-les. Un envoi renvoyé depuis
l'onglet a le même corps et le même X-Webhook-Id.
timestamp est le moment où l'événement s'est produit, pas celui de l'envoi : une nouvelle
tentative, ou un envoi renvoyé depuis l'onglet, le garde.
Chaque événement de paiement porte le lien et le paiement. Le payeur n'est désigné que par name et
email : un envoi ne contient jamais son numéro de téléphone ni sa carte. Ce qui a été saisi au
paiement arrive avec toute suite de sept chiffres ou plus masquée, sous la forme 23••••89, y
compris au milieu d'une phrase ; answers ne contient que les questions du lien, chacune coupée à
500 caractères.
link_uid est l'id du lien dans l'API, et payment.id celui du paiement. quantity_kept est ce
que le paiement détient encore une fois ses remboursements et ses litiges comptés, comme plus haut.
Chaque événement ajoute un objet à côté de payment :
| Événement | Ajoute |
|---|---|
payment.success | Rien de plus |
ticket_purchased | ticket : ticket_type (id, name), quantity, attendees, event_start_date |
donation.received | donation : amount, currency, anonymous, recurring_interval |
order.placed | order : product_type, quantity, variant, shipping_address avec son phone masqué |
payment.refunded | refund : id, amount, currency, reason, created_at. payment.status vaut alors partially_refunded ou refunded. Pour un litige perdu, dispute à la place de refund : id, amount, currency, reason, status, created_at, avec payment.dispute_lost à true |
anonymous signifie que le donateur a demandé à ne pas figurer sur le mur public des donateurs :
vous recevez tout de même son nom. Un litige perdu envoie payment.refunded une fois ; si un
remboursement le règle ensuite, ce remboursement envoie le sien, avec un autre id. link_expired
porte id, event, link_id, link_uid, le lien avec son expiry_date, et timestamp.
Le rapprochement des fonds
Les paiements d'un lien sont des paiements ordinaires. Ils arrivent dans votre liste Payments,
déclenchent payment.succeeded, sont réglés sur votre solde et se remboursent comme les autres : il
n'y a donc rien de propre aux liens à rapprocher.
Le webhook transporte ce que le payeur a saisi dans vos extra_fields, imbriqué sous
metadata.extra_fields, ainsi que le link dont vient le paiement.
Pages associées
- Liens de paiementCe qu'est le produit, et quand un lien vaut mieux qu'une intégration.
- Démarrage rapideCréez un lien, publiez-le, partagez-le.
- PersonnalisationImages, langues, devises, et qui peut entrer.
- WebhooksÉcoutez payment.succeeded et livrez la commande automatiquement.
- Référence API des liens de paiementChaque champ de la ressource, et les cinq endpoints.