Aller au contenu

Performances

Parcourez correctement les listes, mettez en cache les données stables et cadencez vos appels sous les plafonds.

Optimiser les performances d'une intégration de paiement consiste rarement à gagner quelques millisecondes. Il s'agit surtout de ne pas envoyer mille requêtes lorsque trente suffisent, car l'export nocturne consomme le même quota que le checkout utilisé à midi.

Trois sujets couvrent presque tous les besoins : la façon de parcourir une liste, les données que vous pouvez mettre en cache et le rythme auquel vous envoyez vos appels.

Parcourir une liste

Tous les endpoints de liste sont paginés. La réponse contient un tableau items accompagné d'un objet meta, sauf GET /events, dont le tableau s'appelle events. Les paramètres sont identiques partout.

ParamètreTypeValeur par défautRemarques
per_pageinteger25De 1 à 100, et 100 convient presque toujours
pageinteger1Mode par décalage
cursorstringMode par curseur, voir Pagination
date_from, date_toYYYY-MM-DDLimites appliquées à created_at
statusstringUn seul statut, pas une liste

Le mode choisi modifie la structure de meta, ce qui provoque souvent des erreurs.

ModeContenu de meta
Décalagecurrent_page, last_page, per_page, total, next_cursor, has_more
Curseurper_page, next_cursor, prev_cursor, has_more

Écrire la boucle à la main multiplie les risques : erreur de décalage sur la dernière page, compteur qui n'augmente jamais ou liste qui s'allonge pendant son parcours. Chaque SDK fournit un itérateur qui gère ces cas.

# Offset mode: walk until current_page reaches last_page
curl "https://api.wajub.com/payments?per_page=100&page=1&date_from=2026-09-01&date_to=2026-09-30" \
  -H "Authorization: sk_test.xxxxxxxxxxxxxxxx…" \
  | jq '{ count: (.items | length), page: .meta.current_page, last: .meta.last_page }'

Pour un export complet, procédez par périodes plutôt que par pages. Une période d'un mois produit des requêtes bornées et reproductibles. Relancer une période n'est pas perturbé par les objets créés depuis. Parcourir profondément les pages d'une liste qui continue de grandir peut afficher deux fois le même élément ou en ignorer un.

Mettez en cache ce qui ne change pas

Trois endpoints décrivent la plateforme plutôt que votre compte. Ce sont ceux qu'il est utile de mettre en cache.

RessourceEndpointTTL raisonnable
Canaux de paiementGET /channels1 heure
Pays pris en chargeGET /countries24 heures
Devises prises en chargeGET /currencies24 heures

Ce sont aussi les seules routes accessibles à une clé restreinte sans scope, ce qui montre qu'elles contiennent très peu de données sensibles.

Un cache assez simple pour se passer de bibliothèque
const cache = new Map();

async function cached(key, ttlMs, fetcher) {
  const hit = cache.get(key);
  if (hit && Date.now() - hit.at < ttlMs) return hit.value;

  const value = await fetcher();
  cache.set(key, { value, at: Date.now() });
  return value;
}

const channels = await cached('channels:CM', 3_600_000, () =>
  wajub.global.channels({ country: 'CM' }),
);

Cette règle a une conséquence simple : n'utilisez pas non plus le polling. Interroger chaque paiement toutes les quelques secondes est le moyen le plus courant d'atteindre une limite de requêtes sans rien apprendre. Le webhook vous informe déjà. Webhooks ou polling présente les rares cas où une lecture reste adaptée.

Cadencez vos appels à la minute

Cent requêtes simultanées dépassent un plafond, et vous recevez cent réponses 429 au lieu de cent résultats. La parade habituelle est un groupe d'appels : huit en cours, les autres en attente. Elle borne la mauvaise grandeur. L'API ne compte pas les appels que vous avez en cours, elle compte ceux qui arrivent en une minute, et huit appels en cours sur une lecture qui répond en 200 ms font 2 400 appels par minute.

Ce qu'il faut régler est un débit, sous le plafond de la ressource appelée, pour votre plan. Ce sont des appels par minute, et chaque appel sur la ressource compte, lectures comprises, quelle que soit la clé qui l'envoie.

RessourcePay as you goGrowthScaleEnterprise
/payments3090180Aucun plafond
/transfers2060120Aucun plafond
/refunds103060Aucun plafond
/customers50150300Aucun plafond

Limites présente ce tableau, avec le plafond de l'équipe qui s'applique au-dessus.

Votre checkout puise dans le même compteur que votre traitement par lots : un lot qui tourne au plafond ne lui laisse rien. Les quatre cinquièmes du plafond sont une part raisonnable : sur /payments, 24 appels par minute sur Pay as you go, 72 sur Growth, 144 sur Scale.

Une lecture toutes les 2,5 secondes, et l'attente demandée par un 429
import { WajubRateLimitError } from '@wajub/node';

// /payments accepte 30 appels par minute sur Pay as you go : 24 laissent de la place au checkout.
const PER_MINUTE = 24;
const gap = 60_000 / PER_MINUTE;
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

const payments = [];
for (const id of paymentIds) {
  const startedAt = Date.now();
  let payment;

  try {
    payment = await wajub.payments.retrieve(id);
  } catch (err) {
    if (!(err instanceof WajubRateLimitError)) throw err;
    // Autre chose a consommé la minute : attendez le délai donné par l'API, puis relisez une fois.
    await sleep((err.retryAfter ?? 60) * 1000);
    payment = await wajub.payments.retrieve(id);
  }

  payments.push(payment);
  await sleep(Math.max(0, gap - (Date.now() - startedAt)));
}

Un débit aussi bas demande rarement des appels en parallèle : 24 par minute, c'est un appel toutes les 2,5 secondes, et 144 par minute sur Scale, c'est encore un appel toutes les 417 ms. Le SDK Node.js attend déjà le Retry-After d'un 429 et répète une lecture jusqu'à trois fois. Le catch sert au 429 qui revient malgré tout, le plus souvent parce qu'un autre traitement consomme la même minute. Gestion des limites de requêtes présente les trois compteurs et les deux formes d'un 429.

Adaptez les délais d'expiration au parcours critique

Le SDK attend jusqu'à 30 secondes par tentative et relance un appel qui a expiré, jusqu'à trois fois : une API injoignable se traduit par une erreur au bout d'environ deux minutes. Ce délai convient à un job en arrière-plan, mais il est beaucoup trop long pour un client face à un indicateur de chargement. Il rechargera la page et votre handler s'exécutera deux fois. timeout règle l'attente de chaque tentative.

Un délai plus court lorsqu'une personne attend
const payment = await wajub.payments.create(
  { amount: order.total, currency: order.currency, email: order.email },
  { idempotencyKey: `ORDER-${order.id}`, timeout: 8000 },
);

Réduire le délai d'expiration augmente le risque d'un résultat ambigu, car vous abandonnez des requêtes qui étaient sur le point de réussir. C'est précisément pour cette raison que la clé d'idempotence accompagne le même appel. Sans cette clé, chaque requête lente peut provoquer un double débit. Renvoyée pendant que le premier appel est encore en cours, la même clé reçoit un 409 avec errors.idempotency_key : les SDK serveur attendent puis redemandent, et vous obtenez le résultat du premier appel au lieu d'une erreur.

Mesures à suivre

Quatre mesures indiquent si l'intégration fonctionne correctement. Aucune n'est une moyenne.

SignalÀ surveillerAction
Latence de l'APILe p95, pas la moyenneLa moyenne masque les requêtes qui expirent
Taux de 429Toute valeur durablement supérieure à zéroUne boucle existe, trouvez-la avant qu'elle ne s'aggrave
Taux de 5xxUne hausse, pas un pic isoléUn cas est du bruit, une tendance indique un incident
Retard des webhooksDe l'horodatage de l'événement à son traitementUn écart croissant signifie que votre file prend du retard

Konsole fournit une vue par requête pour un paiement donné. Surveillance explique comment relier ces mesures à votre propre système d'alerte.

Que pensez-vous de ce contenu ?