Remboursement PIX
Initie un remboursement (total ou partiel) d'une transaction PIX reçue.
Endpoint
POST /api/external/pix/refundHeaders
| Header | Type | Obligatoire | Description |
|---|---|---|---|
Authorization | String | Oui | ApiKey {client_id}:{client_secret} |
Content-Type | String | Oui | application/json |
hmac | String | Oui | Signature HMAC-SHA512 du body (en savoir plus) |
Idempotency-Key | String | Non | Clé unique pour éviter le traitement en double (max 256 caractères) |
X-Key-Case | String | Non | Définissez à camelCase pour recevoir les champs de la réponse en camelCase (par défaut snake_case) |
Request Body
| Champ | Type | Obligatoire | Description | Exemple |
|---|---|---|---|---|
original_transaction_id | String | Oui | ID de la transaction PIX originale reçue (voir alias ci-dessous) | "7popu57v6us7p6pcicgq12345" |
amount | Integer | Non | Valeur à rembourser en centavos dans la requête. Si omis, rembourse la valeur totale de la transaction originale. | 3000 (R$ 30,00) |
reason | String | Non | Code de remboursement BACEN (voir tableau ci-dessous). Par défaut : MD06. Non validé localement — envoyé directement au BACEN. | "MD06" |
description | String | Non | Description 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éfixeE) de la transaction originaleoriginalTransactionId— camelCasetransaction_id— fallbackend_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 cetend_to_end_idoriginalremaining_refundable=amount_original - total_refunded(combien peut encore être remboursé)is_partial=truesi 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
| Code | Description |
|---|---|
MD06 | Remboursement par accord entre les parties |
BE08 | Fraude |
AM09 | Valeur incorrecte |
SL02 | Erreur de liquidation |
RR04 | Transaction non reconnue (BACEN) |
FR01 | Fraude 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) pourMD06,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
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.
{
"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.
{
"worked": true,
"transaction_id": "PIXRETD24313102202604071509K14UmbMt6ck",
"end_to_end_id": "D24313102202604071509K14UmbMt6ck",
"amount": 300000,
"status": "settled",
"detail": "Devolução liquidada"
}| Champ | Type | Description |
|---|---|---|
worked | Boolean | true indique que la requête a été acceptée |
transaction_id | String | Identifiant du remboursement (préfixe PIXRET). Utilisez pour consulter le status. |
end_to_end_id | String | End-to-End ID du remboursement dans le SPI/BACEN (préfixe D, pas E) |
amount | Integer | Valeur du remboursement en subcentavos (÷ 10 000 pour reais). 300000 = R$ 30,00 |
status | String | accepted (HTTP 202 — en traitement) ou settled (HTTP 200 — liquidé). Jamais processing dans ce champ du POST. |
detail | String | Message descriptif |
Réponse d'erreur (404)
{
"worked": false,
"errors": {
"not_found": "Transação original não encontrada"
}
}Réponse d'erreur (422) -- Solde insuffisant
{
"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
{
"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
{
"worked": false,
"errors": {
"bad_request": "original_transaction_id is required"
}
}Réponse d'erreur (401)
{
"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 originaleBE08(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.