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éePAYOUT_INITIATE) renvoie le plus gros pay-out quePOST /v1/pay-outaccepterait maintenant dans cette devise (availableAmountMajor), le plafond par pay-out (maxSinglePayoutMajor) et la règle qui borne (limitingFactor). Lecture indicative :POST /v1/pay-outrevé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 queGET /v1/transactions/{orderId}, adressé par l'identifiant az54 (transactionIdsurPOST /v1/pay-in). Il est unique toutes organisations confondues : il n'a pas le404d'ambiguïté de l'orderId. Mêmes données masquées, et le plafond de 30 requêtes par minute et partransactionId.
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
403avec lestatus<PORTÉE>_NOT_ALLOWED(PAYOUT_INITIATE_NOT_ALLOWED, par exemple) ; - une clé avec
PAYOUT_INITIATEsansPAYOUT_APPROVEdépose un brouillon :POST /v1/pay-outrépond201avecstatus: "pending_approval", et le brouillon non approuvé passe encancelledau-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 prendGET /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_timestampet donc unesignaturedifférents à chaque tentative. Ne dédupliquez pas sur ces champs. - Journal des tentatives :
GET /v1/transactions/by-id/{transactionId}renvoie un tableaunotifications[]— 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 un409 CONFLICTtant 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,failureCoden'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.
