Skip to content

Remboursement PIX

Initie un remboursement (total ou partiel) d'une transaction PIX reçue.

Endpoint

POST /api/external/pix/refund

Headers

HeaderTypeObligatoireDescription
AuthorizationStringOuiApiKey {client_id}:{client_secret}
Content-TypeStringOuiapplication/json
hmacStringOuiSignature HMAC-SHA512 du body (en savoir plus)
Idempotency-KeyStringNonClé unique pour éviter le traitement en double (max 256 caractères)
X-Key-CaseStringNonDéfinissez à camelCase pour recevoir les champs de la réponse en camelCase (par défaut snake_case)

Request Body

ChampTypeObligatoireDescriptionExemple
original_transaction_idStringOuiID de la transaction PIX originale reçue (voir alias ci-dessous)"7popu57v6us7p6pcicgq12345"
amountIntegerNonValeur à rembourser en centavos dans la requête. Si omis, rembourse la valeur totale de la transaction originale.3000 (R$ 30,00)
reasonStringNonCode de remboursement BACEN (voir tableau ci-dessous). Par défaut : MD06. Non validé localement — envoyé directement au BACEN."MD06"
descriptionStringNonDescription du remboursement. Utilisée comme return_reason dans le BACEN (jusqu'à 140 caractères). Par défaut : "Devolução PIX"."Remboursement demandé par le client"

Alias acceptés

Pour la flexibilité d'intégration, le backend accepte plusieurs noms pour chaque champ (premier trouvé l'emporte) :

  • original_transaction_id (canonique)
    • original_e2e_id — accepte l'E2E ID (préfixe E) de la transaction originale
    • originalTransactionId — camelCase
    • transaction_id — fallback
    • end_to_end_id — fallback
  • reason (canonique)
    • return_code — alias direct

Validation de propriété

Le backend valide que la transaction originale appartient au même merchant que l'API Key qui appelle l'endpoint. Si vous tentez de rembourser une transaction d'un autre merchant, vous recevez HTTP 404 "original transaction not found" — même réponse que pour une transaction inexistante (par sécurité).

Valeurs monétaires (requête × réponse)

Le amount de la requête est en centavos (R$ 1,00 = 100). Le amount de la réponse et des webhooks est en subcentavos / unités de base (R$ 1,00 = 10000). Exemple : envoyez 3000 pour rembourser R$ 30,00 ; la réponse retourne 300000.

N'envoyez JAMAIS float/decimal. Envoyez toujours un entier en centavos dans la requête et divisez les valeurs de réponse par 10 000 pour l'affichage BRL.

Remboursement partiel -- comment suivre la valeur restante

Pour un remboursement partiel, fournissez un amount inférieur à la valeur originale. La valeur totale des remboursements d'une même transaction ne peut pas excéder la valeur originale reçue.

Le backend suit l'accumulé dans total_refunded et remaining_refundable dans les webhooks pix.refund.completed et pix.return.received :

  • total_refunded = somme de tous les remboursements déjà exécutés pour cet end_to_end_id original
  • remaining_refundable = amount_original - total_refunded (combien peut encore être remboursé)
  • is_partial = true si ce remboursement était partiel

Avant de faire un deuxième remboursement partiel, consultez le dernier webhook pix.refund.completed pour connaître le remaining_refundable — si vous dépassez, le backend retourne HTTP 422 « Valor da devolução excede o valor original da transação ».

Codes de remboursement

CodeDescription
MD06Remboursement par accord entre les parties
BE08Fraude
AM09Valeur incorrecte
SL02Erreur de liquidation
RR04Transaction non reconnue (BACEN)
FR01Fraude signalée côté bénéficiaire (code BACEN observé dans les flux MED)

Validation du reason code

Owem ne valide pas le champ reason localement. Le code est transmis au BACEN via PACS.004.

  • Codes BACEN valides pour remboursement PACS.004 : MD06, BE08, AM09, SL02, RR04, FR01.
  • Descriptions amicales dans le webhook pix.return.received : le backend maintient une carte locale (return_reasons) pour MD06, BE08, FR01, SL02. Les codes hors de cette carte (ex. : RR04, AM09) tombent dans la description générique "Return from counterparty ({code})" dans le webhook.

Le controller accepte également return_code comme alias de reason.

Si vous envoyez un code non-BACEN, le remboursement sera rejeté par le BACEN ultérieurement (422 asynchrone via webhook pix.payout.failed avec reason_code renseigné).

Exemple

bash
BODY='{"amount":3000,"description":"Remboursement accord","original_transaction_id":"7popu57v6us7p6pcicgq12345","reason":"MD06"}'
HMAC=$(echo -n "$BODY" | openssl dgst -sha512 -hmac "$CLIENT_SECRET" | awk '{print $2}')

curl -X POST https://api.owem.com.br/api/external/pix/refund \
  -H "Authorization: ApiKey $CLIENT_ID:$CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -H "hmac: $HMAC" \
  -d "$BODY"

Réponse de succès -- 202 (async)

Chemin le plus commun — remboursement accepté et mis en file.

json
{
  "worked": true,
  "transaction_id": "PIXRETD24313102202604071509K14UmbMt6ck",
  "end_to_end_id": "D24313102202604071509K14UmbMt6ck",
  "amount": 300000,
  "status": "accepted",
  "detail": "Devolucao processada"
}

HTTP 202 — suivez le résultat via GET /transactions/:id (utilisez le transaction_id retourné) ou attendez le webhook pix.refund.completed / pix.payout.returned.

E2E ID avec préfixe D

Contrairement à une transaction PIX OUT commune (E2E préfixe E), un remboursement a un E2E avec préfixe D (de « Devolução »). Ce préfixe identifie le type ISO 20022 pacs.004 dans le BACEN. Le transaction_id interne a également le préfixe PIXRET pour faciliter l'identification.

Réponse de succès -- 200 (fast-track)

Chemin rare — remboursement liquidé de façon synchrone avant le retour de la réponse HTTP.

json
{
  "worked": true,
  "transaction_id": "PIXRETD24313102202604071509K14UmbMt6ck",
  "end_to_end_id": "D24313102202604071509K14UmbMt6ck",
  "amount": 300000,
  "status": "settled",
  "detail": "Devolução liquidada"
}
ChampTypeDescription
workedBooleantrue indique que la requête a été acceptée
transaction_idStringIdentifiant du remboursement (préfixe PIXRET). Utilisez pour consulter le status.
end_to_end_idStringEnd-to-End ID du remboursement dans le SPI/BACEN (préfixe D, pas E)
amountIntegerValeur du remboursement en subcentavos (÷ 10 000 pour reais). 300000 = R$ 30,00
statusStringaccepted (HTTP 202 — en traitement) ou settled (HTTP 200 — liquidé). Jamais processing dans ce champ du POST.
detailStringMessage descriptif

Réponse d'erreur (404)

json
{
  "worked": false,
  "errors": {
    "not_found": "Transação original não encontrada"
  }
}

Réponse d'erreur (422) -- Solde insuffisant

json
{
  "worked": false,
  "errors": {
    "unprocessable_entity": "insufficient balance"
  }
}

Se produit quand le solde sécurisé (min(TB, PG) — voir Solde) est inférieur au amount demandé. Le remboursement exige que le compte d'origine ait un solde ≥ la valeur à rembourser.

Réponse d'erreur (422) -- Valeur dépassée

json
{
  "worked": false,
  "errors": {
    "unprocessable_entity": "Valor da devolução excede o valor original da transação"
  }
}

Se produit quand le total des remboursements (actuel + précédents) dépasserait le amount de la transaction originale. Les remboursements partiels sont autorisés, mais la somme ne peut pas dépasser la valeur reçue.

Réponse d'erreur (400) -- original_transaction_id absent

json
{
  "worked": false,
  "errors": {
    "bad_request": "original_transaction_id is required"
  }
}

Réponse d'erreur (401)

json
{
  "worked": false,
  "errors": {
    "unauthorized": "Missing API key credentials. Use Authorization: ApiKey <client_id>:<client_secret>"
  }
}

Délai pour remboursement (BACEN)

Délais définis par le BACEN (non enforced localement par le backend Owem — envoyé directement au SPI et validé là) :

  • MD06 (accord) : jusqu'à 90 jours calendaires après réception de la transaction originale
  • BE08 (fraude) : suivant les délais du MED réglementaire BACEN (flux de contestation, voir Infractions)
  • FR01 (fraude signalée) : même délai MED BACEN
  • Autres codes (AM09, SL02, RR04) : validation au cas par cas au BACEN ; pour la logique opérationnelle, traitez comme délai maximum de 30 jours

Le backend ne bloque pas les remboursements hors délai — ils passent dans le POST (HTTP 202 accepted) et sont rejetés ensuite par le BACEN via webhook pix.payout.failed avec reason_code spécifique. Votre système doit valider l'âge de la transaction originale avant d'appeler l'API si vous avez besoin de fail-fast.

PACS.004 x MED : remboursements initiés par infraction BACEN

Si le remboursement que vous initiez est la conséquence d'une infraction BACEN (notification via pix.refund.requested), n'appelez pas cet endpoint. Le remboursement MED est exécuté automatiquement par le backend quand analysis_result=AGREED est décidé (défense non soumise dans les délais OU acquiescement manuel via le merchant portal). Appeler POST /pix/refund en parallèle peut générer des duplications.

Cet endpoint est pour des remboursements volontaires initiés par vous (ex. : rembourser un PIX reçu en trop, annuler une vente, ajustement commercial). Voir Infractions (flux complet) pour distinguer les deux scénarios.

Owem Pay Instituição de Pagamento — ISPB 37839059