Changelog de l'API

Les changements de contrat, du plus récent au plus ancien.

Ne sont listés ici que les changements observables depuis une intégration. Les corrections internes n'y figurent pas.

2026-10-15 — otpLength sur GET /v1/corridors/available

Chaque élément de operators peut porter un entier otpLength : la longueur exacte de l'OTP que le canal exige (6 chiffres sur Pawapay). Absent, l'OTP fait 4 à 6 chiffres, le PSP retenu n'étant pas connu à la saisie. Borner la saisie sur cette valeur évite un refus du PSP.

Ajout compatible, aucune action requise.

2026-10-07 — plafond de pay-out et reçu public par id

Deux endpoints, déjà en service, entrent au contrat.

  • GET /v1/account-limits/payout?currency=XOF (clé d'API, portée PAYOUT_INITIATE) renvoie le plus gros pay-out que POST /v1/pay-out accepterait maintenant dans cette devise (availableAmountMajor), le plafond par pay-out (maxSinglePayoutMajor) et la règle qui borne (limitingFactor). Lecture indicative : POST /v1/pay-out revérifie, et un autre pay-out peut consommer le quota entre les deux appels. Voir la référence.
  • GET /v1/transactions/receipt/{transactionId} (sans authentification) renvoie le même reçu public que GET /v1/transactions/{orderId}, adressé par l'identifiant az54 (transactionId sur POST /v1/pay-in). Il est unique toutes organisations confondues : il n'a pas le 404 d'ambiguïté de l'orderId. Mêmes données masquées, et le plafond de 30 requêtes par minute et par transactionId.

Ajouts compatibles, aucune action requise.

2026-09-30 — payerName sur POST /v1/pay-in

POST /v1/pay-in accepte un champ optionnel payerName : le nom de la personne ou de l'entité qui paie, 100 caractères au plus. Il est restitué dans payerName (string ou null) par GET /v1/transactions et GET /v1/transactions/by-id/{transactionId}, pour rapprocher un encaissement sans rouvrir la transaction.

Le champ est null sur les pay-outs, et sur les pay-ins créés sans lui ou avant ce changement : les PSP ne renvoient que le numéro de téléphone, donc aucun historique n'est rétabli. Il ne figure ni dans le webhook sortant ni sur le reçu public : le reçu d'un lien de paiement porte le nom que le payeur y a saisi, jamais celui d'un pay-in créé par l'API.

Côté SDK, PayinForm (@az54/react) accepte showPayerName pour afficher le champ, masqué par défaut ; la valeur saisie remonte dans PayinFormData.payerName. Ajout compatible, aucune action requise.

2026-09-30 — description sur GET /v1/transactions

Chaque transaction renvoyée par GET /v1/transactions et GET /v1/transactions/by-id/{transactionId} porte un champ description (string ou null) : la valeur transmise à la création du pay-in ou du pay-out. Ajout compatible, aucune action requise.

2026-09-30 — redirectUrls : longueur bornée et retour par défaut

Sur certains corridors, une URL de redirectUrls de plus de 120 caractères est désormais refusée en 400 VALIDATION_ERROR au lieu d'aboutir à un pay-in FAILED. Aucune transaction n'est créée : corrigez l'URL et rejouez avec le même orderId.

Quand le payeur ne peut pas être renvoyé sur la bonne URL (redirectUrls absentes, ou prestataire sans URL d'échec), il revient sur la page reçu az54, qui affiche le statut réel. Avant, il pouvait arriver sur votre URL de succès après un échec, ou sur une page introuvable. Voir Workflows.

2026-09-05 — un environnement sandbox

Un second serveur, https://api.sandbox.az54.com, existe en plus de https://api.az54.com. Il reprend le même catalogue de corridors qu'en production, mais aucun opérateur réel n'y est joignable : l'issue d'un pay-in ou d'un pay-out y est pilotée par les deux derniers chiffres du numéro appelé — voir Numéros de test.

Une clé n'est valide que sur le serveur où elle a été émise : sk_test_ sur le sandbox, sk_live_ en production. Présentée sur l'autre serveur, elle est refusée en 401. Aucun changement pour les clés sk_live_ existantes.

Aucun tableau de bord ne couvre encore le sandbox : une clé sk_test_ s'obtient aujourd'hui auprès du support az54, pas en libre-service.

2026-09-04 — portées des clés d'API

Une clé d'API reçoit à sa création un sous-ensemble de portées : PAYIN_INITIATE, PAYOUT_INITIATE, PAYOUT_APPROVE, TRANSACTIONS_VIEW, WEBHOOKS_MANAGE. Deux effets observables :

  • un appel qui touche une portée absente de la clé est refusé en 403 avec le status <PORTÉE>_NOT_ALLOWED (PAYOUT_INITIATE_NOT_ALLOWED, par exemple) ;
  • une clé avec PAYOUT_INITIATE sans PAYOUT_APPROVE dépose un brouillon : POST /v1/pay-out répond 201 avec status: "pending_approval", et le brouillon non approuvé passe en cancelled au-delà de 7 jours.

Les clés émises avant ce changement ont reçu toutes les portées : aucune intégration existante ne change de comportement. Une clé créée ensuite sans portée ne peut rien faire. Voir Authentification et Erreurs.

2026-09-02 — currencyCode et transactionId sur le webhook sortant

Le corps du webhook sortant porte deux champs de plus :

  • currencyCode, la devise d'amount (XOF, XAF, CDF, USDC) ;
  • transactionId, l'identifiant az54 de la transaction — celui que prend GET /v1/transactions/by-id/{transactionId}, pas la référence du PSP.

Rapprocher une notification ne demande donc plus de relire la transaction par orderId.

Ajout additif, mais avec une conséquence sur la signature : tout champ du corps entre dans la chaîne signée. Un vérificateur qui parcourt le corps reçu — la forme publiée dans Webhook sortant — n'a rien à changer. Un vérificateur qui reconstruit la chaîne depuis une liste de champs codée en dur calcule désormais un HMAC qui ne correspond plus, et rejettera vos notifications comme non authentiques.

2026-09-02 — le webhook sortant est rejoué, journalisé et renvoyable

Une notification qui n'aboutit pas n'est plus perdue définitivement.

  • Rejeu automatique jusqu'à 6 tentatives : immédiatement, puis +30 s, +2 min, +10 min, +1 h, +6 h. La chaîne s'arrête au premier 2xx. Rien à changer côté intégration, sinon vous assurer que votre handler est bien idempotent : une même issue peut désormais vous être livrée plusieurs fois, avec un _nonce, un _timestamp et donc une signature différents à chaque tentative. Ne dédupliquez pas sur ces champs.
  • Journal des tentatives : GET /v1/transactions/by-id/{transactionId} renvoie un tableau notifications[] — code HTTP, motif d'échec, durée. Les échecs jusqu'ici muets (pas d'URL, pas de secret actif, URL bloquée) y apparaissent explicitement.
  • Renvoi en libre-service : POST /v1/transactions/by-id/{transactionId}/resend-callback, refusé par un 409 CONFLICT tant que la transaction n'est pas dans un état terminal.

Voir Réconciliation.

2026-09-02 — lecture des transactions par clé d'API

GET /v1/transactions accepte désormais une clé d'API, là où il exigeait un jeton de session, et gagne un filtre orderId en correspondance exacte. S'y ajoute GET /v1/transactions/by-id/{transactionId}, qui rend le détail d'une transaction et le journal de ses notifications.

Surface ajoutée : aucun changement pour les intégrations existantes. Les plafonds du reçu public n'ont pas bougé — c'est bien la lecture authentifiée qui est le canal de rattrapage, pas lui.

2026-09-02 — rotation des secrets de webhook par clé d'API

GET /v1/webhook-secrets et DELETE /v1/webhook-secrets/{secretId} acceptent la clé d'API, à parité avec POST : la rotation d'un secret est automatisable sans passer par le tableau de bord.

Le choix du secret signataire est par ailleurs devenu déterministe — c'est le plus ancien secret actif de l'organisation qui signe. Deux effets observables :

  • créer un secret ne bascule plus la signature sous vos pieds : créez, déployez le nouveau vérificateur, révoquez ensuite ;
  • un secret créé par un autre membre de votre organisation signe désormais vos notifications. La recherche était auparavant scopée à l'utilisateur : un tel secret apparaissait actif sans jamais signer, donc aucune notification n'était émise.

2026-08-28 — nouveau champ failedAt sur GET /v1/transactions

Chaque transaction listée par GET /v1/transactions porte un champ failedAt : l'instant où elle est passée en failed ou cancelled, null tant qu'elle n'a pas échoué. Il se distingue d'updatedAt, qui suit la dernière écriture quelle qu'elle soit.

Ajout purement additif. Voir Causes d'échec : sur l'historique antérieur à cette date, la valeur a été reprise d'updatedAt et n'est donc qu'une approximation.

2026-08-28 — nouveau champ failureCode sur les échecs

Le webhook sortant et les réponses synchrones de POST /v1/pay-in et POST /v1/pay-out portent un champ failureCode de plus sur un statut terminal en échec : le code brut du PSP, isolé du message que failureReason continue de porter fusionné.

Ajout purement additif — rien à changer côté intégration. Deux points :

  • comme failureReason, failureCode n'est pas contractuel : chaque PSP a son propre référentiel. Voir Causes d'échec ;
  • il est absent des transactions antérieures à cette date, et de tout échec où le PSP n'a renvoyé qu'un message. Il n'y a pas eu de rétro-remplissage.

2026-08-12 — orderId n'est plus unique globalement

L'unicité d'un orderId est désormais garantie par organisation, et non plus sur l'ensemble de la plateforme.

Aucun changement à faire côté intégration : votre orderId reste unique chez vous, et 409 CONFLICT continue de signaler un rejeu.

Une seule conséquence est visible, sur le reçu public : il n'a pas d'organisation à laquelle se rattacher, et répond donc 404 sur un orderId ambigu.

On this page