Déclencher des retraits
Un retrait envoie des fonds depuis le solde available de votre workspace vers une adresse externe.
C’est la moitié du produit où vivent les contrôles de sécurité : ce guide couvre donc le mur que
vous rencontrerez avant la première requête, pas seulement la requête elle-même.
Les retraits sont un flux de production : les clés Sandbox ne reçoivent volontairement pas les scopes de retrait (le pourquoi est dans Passage en production), et la liste d’autorisation ci-dessous s’applique sur le mainnet.
Avant le premier retrait : la liste d’autorisation
Section intitulée « Avant le premier retrait : la liste d’autorisation »Un workspace en production démarre avec la liste d’autorisation des retraits ACTIVE. Un retrait
vers une adresse qui n’est pas un bénéficiaire approuvé est refusé en 422, quelle que soit la
qualité du reste de la requête.
Les bénéficiaires se gèrent sur la page Security du tableau de bord, pas par cette API, parce
qu’ajouter une destination de paiement est une action protégée : elle exige un second facteur fort,
et la nouvelle adresse ne devient utilisable qu’après un délai de sécurité (24 h par défaut).
Vos serveurs et vos propriétaires en sont informés : les webhooks BENEFICIARY_ADDED et
BENEFICIARY_REMOVED partent, et tout changement qui affaiblit la politique déclenche
WITHDRAWAL_POLICY_CHANGED avec data.weakened: true.
Anticipez : ajoutez les bénéficiaires que vous paierez avant d’en avoir besoin, pour que le délai de sécurité soit écoulé au moment du premier vrai retrait.
Connaître le coût avant de vous engager
Section intitulée « Connaître le coût avant de vous engager »POST /v2/fees/quote (scope fees:read) chiffre un retrait avec le moteur exact qui le réglera :
envoyez direction, chain, asset et amount, et lisez les frais et le net. Utilisez
mode: "gross_up" pour la question inverse : combien retirer pour que le destinataire touche un
montant cible. Le devis est indicatif ; les frais engageants sont calculés au règlement.
Demander le retrait
Section intitulée « Demander le retrait »POST /v2/withdrawals (scope withdrawals:write). Envoyez toujours un en-tête Idempotency-Key ;
la section cohérence plus bas repose dessus.
Requête :
{ "chain": "TRON", "asset": "USDT_TRON", "amount": "50", "destination_address": "TExternalRecipientAddress123456789"}Réponse (201) :
{ "id": "e5f6a7b8-0000-0000-0000-000000000005", "chain": "TRON_MAINNET", "asset": "USDT_TRON", "amount": "50.000000", "fee": "0.900000", "net_amount": "49.100000", "status": "PENDING", "idempotency_key": "3e1f8a20-6a5c-4d5e-9b2a-8c7d6e5f4a3b", "requested_at": "2026-08-09T11:00:00.000Z"}Le montant demandé est verrouillé immédiatement : il passe d’available à locked dès que la
requête est acceptée, pour qu’il ne puisse pas être dépensé deux fois pendant que le retrait est en
vol. fee et net_amount sont des estimations à ce stade ; les chiffres définitifs arrivent avec
WITHDRAWAL_CONFIRMED.
Suivre le retrait : chaque statut, et le webhook qui vous l’annonce
Section intitulée « Suivre le retrait : chaque statut, et le webhook qui vous l’annonce »| Statut | Signification | Webhook | Final ? |
|---|---|---|---|
PENDING |
Accepté, fonds verrouillés, en file pour diffusion | (la réponse de création elle-même) | non |
PENDING_APPROVAL |
Au-dessus du seuil d’approbation, en attente d’un second membre | WITHDRAWAL_PENDING_APPROVAL |
non |
UNDER_REVIEW |
Destination retenue pour revue de conformité | WITHDRAWAL_HELD |
non |
PROCESSING |
Signé et diffusé ; txid est désormais renseigné |
WITHDRAWAL_PROCESSING |
non |
CONFIRMING |
Accumule les confirmations on-chain | (aucun) | non |
CONFIRMED |
Confirmé on-chain ; frais définitifs, détail dans le payload | WITHDRAWAL_CONFIRMED |
oui |
FAILED |
Échec on-chain ; les fonds verrouillés reviennent en available |
WITHDRAWAL_FAILED |
oui |
CANCELLED |
Annulé avant diffusion ; les fonds reviennent en available |
WITHDRAWAL_CANCELLED |
oui |
Traitez CONFIRMED, FAILED et CANCELLED comme finaux : ils ne changeront plus. Tout le reste
est transitoire, et l’issue terminale arrive toujours sous l’une de ces trois formes.
Approbations et retenues
Section intitulée « Approbations et retenues »Deux des états transitoires impliquent des humains, et votre intégration doit s’attendre aux deux :
PENDING_APPROVAL(double validation). Si la politique de votre workspace fixe un seuil d’approbation, un retrait au-dessus attend l’approbation d’un second membre dans le tableau de bord ; le membre qui l’a créé ne peut jamais être son approbateur. Approuvé, il repasse enPENDINGet suit le flux normal. Rejeté, il termineCANCELLEDavec les fonds restitués etWITHDRAWAL_CANCELLEDpart. Resté au-delà de la fenêtre d’approbation, il expire et les fonds verrouillés reviennent enavailable; sondez le retrait si vous devez détecter ce cas rapidement.UNDER_REVIEW(retenue de conformité). Une destination qui correspond à une liste de filtrage retient le retrait pour revue au lieu de le signer. Libéré, il poursuit ; rejeté, il termineCANCELLEDavec les fonds restitués. Le payload deWITHDRAWAL_HELDporte le statut et les champs standard, rien sur le filtrage lui-même.
Annuler tant que c’est possible
Section intitulée « Annuler tant que c’est possible »DELETE /v2/withdrawals/{id} annule un retrait qui n’a pas été diffusé (PENDING ou
PENDING_APPROVAL). Les fonds verrouillés reviennent en available et WITHDRAWAL_CANCELLED
part. Une fois PROCESSING, la transaction est sur le réseau et ne peut plus être annulée ;
attendez l’état terminal.
Gérer les refus au moment de la requête
Section intitulée « Gérer les refus au moment de la requête »Chaque refus porte un code ; branchez-vous dessus, jamais sur le message. Ceux que cet endpoint
renvoie, avec la conduite à tenir, sont regroupés dans le
catalogue des codes d’erreur ; les plus fréquents sont le solde
insuffisant, une destination mal formée pour la blockchain, une destination qui est une adresse
gérée par CowriePay (retirez vers des adresses externes uniquement), une blockchain pas encore
ouverte, un gel de conformité du workspace, et les refus mainnet de liste d’autorisation et de
limites décrits plus haut.
Garantir la cohérence
Section intitulée « Garantir la cohérence »L’échec qui coûte de l’argent est l’échec ambigu : vous avez envoyé la requête, la connexion est
morte, et vous ignorez si le retrait existe. L’en-tête Idempotency-Key existe exactement pour
cela ; envoyez-le à chaque requête et l’ambiguïté disparaît :
key = stored_key_for(payout) # générée une fois par intention, persistéeresponse = POST /v2/withdrawals (Idempotency-Key: key)
si timeout ou erreur réseau : réessayez le POST avec la MÊME clé -> 201 : il n'existait pas ; créé maintenant -> 200 + Idempotent-Replay: true : il existait ; voici l'original -> 409 : encore en vol ; patientez, réessayez la MÊME clé -> 422 IDEMPOTENCY_MISMATCH : votre corps de retentative diffère ; bug de votre côté, ne forcez pasNe générez jamais une clé neuve à la retentative : c’est ainsi qu’un paiement devient deux. Si une
requête est partie sans clé et sans réponse, réconciliez avant de réessayer : listez les retraits
récents (GET /v2/withdrawals) et cherchez votre montant et votre destination avant de créer quoi
que ce soit.
Et ensuite
Section intitulée « Et ensuite »- Webhooks : la vérification de signature et le contrat de déduplication sur lesquels repose votre suivi de statuts.
- Encaisser des dépôts : l’autre moitié du flux d’argent.
- Passage en production : la liste de contrôle complète du premier jour, liste d’autorisation comprise.