PIX Cash-In -- Générer un QR Code
Génère une facturation PIX avec QR Code pour la réception de valeurs sur le compte associé à votre API Key.
Endpoint
POST /api/external/pix/cash-inHeaders
| 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 |
|---|---|---|---|---|
amount | Integer | Oui | Valeur en centavos (R$ 30,00 = 3000) | 3000 |
description | String | Non | Description de la facturation. Si omis, la valeur par défaut est "Cobranca PIX". | "Commande #1234" |
external_id | String | Non | Identifiant de votre système pour le suivi. Max 128 caractères. Uniquement a-zA-Z0-9._:- (les valeurs hors pattern sont silencieusement écartées et le champ revient en null -- voir avertissement ci-dessous). Retourné dans les réponses et webhooks. | "order-9876" |
pix_key | String | Non | Clé PIX spécifique pour générer le QR Code. Si omise, utilise la clé active la plus récente du compte (ordre par inserted_at DESC). | "12345678901" |
city | String | Non | Ville du bénéficiaire dans le QR Code. Par défaut : SAO PAULO. Tronquée automatiquement à 15 caractères. | "RIO DE JANEIRO" |
Envoyez amount TOUJOURS en entier (en centavos)
Le champ amount DOIT être un entier en centavos. N'envoyez JAMAIS des valeurs float/decimal :
- ✅
"amount": 3000→ R$ 30,00 - ❌
"amount": 30.00→ interprété comme30centavos = R$ 0,30 (facturation 100× plus petite que voulue) - ❌
"amount": 30→ R$ 0,30 (également incorrect)
En JavaScript, convertissez toujours avec Math.round avant d'envoyer :
const valeurEnReais = 30.0;
const amount = Math.round(valeurEnReais * 100); // 3000En Python : amount = round(valeur_en_reais * 100). En Go : amount := int(math.Round(valeurEnReais * 100)).
Valeurs monétaires (entrée × réponse)
Les valeurs d'entrée sont en centavos (R$ 1,00 = 100). Les valeurs de réponse sont en unités de base (R$ 1,00 = 10000). Pour convertir la réponse en reais, divisez par 10 000. N'utilisez jamais de virgule flottante dans aucun sens.
external_id invalide = rejet silencieux
Si le external_id contient des caractères hors de a-zA-Z0-9._:-, dépasse 128 octets, ou ne contient que des espaces blancs, le serveur rejette silencieusement la valeur et la réponse revient avec "external_id": null (aucune erreur n'est retournée). Vérifiez toujours le external_id présent dans la réponse avant de l'utiliser pour la conciliation -- s'il est null, votre identifiant a été rejeté par la validation.
Exemple
BODY='{"amount":3000,"description":"Commande #1234","external_id":"order-9876"}'
HMAC=$(echo -n "$BODY" | openssl dgst -sha512 -hmac "$CLIENT_SECRET" | awk '{print $2}')
curl -X POST https://api.owem.com.br/api/external/pix/cash-in \
-H "Authorization: ApiKey $CLIENT_ID:$CLIENT_SECRET" \
-H "Content-Type: application/json" \
-H "hmac: $HMAC" \
-d "$BODY"Réponse de succès (200)
{
"worked": true,
"transaction_id": "7popu57v6us7p6pcicgq12345",
"qr_code": "00020126580014br.gov.bcb.pix...",
"qr_code_image": "data:image/png;base64,iVBORw0KGgo...",
"external_id": "order-9876",
"amount": 300000,
"status": "active",
"type": "dynamic",
"location_url": "https://qrcode.owem.com.br/pix/v2/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"expires_at": "2026-03-07T16:30:00Z"
}| Champ | Type | Description |
|---|---|---|
worked | Boolean | true indique le succès de l'opération |
transaction_id | String | null | Identifiant unique de la facturation (tx_id du QR Code, alphanumérique max 35 caractères selon spec BACEN). null uniquement en cas de défaillances internes rares de persistance |
qr_code | String | null | Code EMV copier-coller pour paiement. null (valeur JSON littérale, pas la chaîne "null") uniquement en cas de défaillances internes rares de persistance |
qr_code_image | String | Image du QR Code encodée en base64 (PNG, préfixe data:image/png;base64,). Chaîne vide ("") en cas de défaillances internes rares |
external_id | String | null | Votre identifiant, retourné tel qu'envoyé. null s'il n'a pas été renseigné, s'il est invalide (voir avertissement ci-dessus), ou s'il a été rejeté par la sanitisation |
amount | Integer | Valeur de la facturation en unités de base (÷ 10 000 pour reais). 300000 = R$ 30,00 |
status | String | Status initial toujours active (QR Code actif pour paiement). L'évolution ultérieure (paid/expired/cancelled) est visible dans les consultations et webhooks (voir cycle de vie du QR) |
type | String | null | Type du QR Code : dynamic (par défaut depuis avril/2026) ou static. Dynamique exige que la banque du payeur consulte location_url et valide le JWS |
location_url | String | null | URL de consultation du payload dynamique du BACEN (ex. : https://qrcode.owem.com.br/pix/v2/{uuid}). Ce n'est pas le JWS lui-même -- la banque du payeur fait un GET sur cet endpoint pour recevoir le payload ISO 20022 signé (JWS PS256). Présent uniquement en QR dynamique ; null en statique |
expires_at | String | null | Date/heure d'expiration du QR Code en ISO 8601 UTC avec suffixe Z (ex. : 2026-03-07T16:30:00Z). null si QR sans expiration |
QR dynamique est le défaut
Tous les comptes en production utilisent QR dynamique par défaut depuis avril/2026. Le QR dynamique inclut une URL (location_url) que la banque du payeur consulte pour obtenir le payload signé via JWS -- compatibilité maximisée avec les banques BACEN-strict.
Réponse d'erreur (400)
{
"worked": false,
"detail": "O campo amount é obrigatório"
}Réponse d'erreur (401)
{
"error": {
"status": 401,
"message": "Missing API key credentials. Use Authorization: ApiKey <client_id>:<client_secret>"
}
}Réponse d'erreur (422)
{
"worked": false,
"detail": "Invalid HMAC signature"
}Flux recommandé
- Générez la facturation avec cet endpoint
- Affichez le QR Code (
qr_code_image) ou le code copier-coller (qr_code) au payeur - Recevez la confirmation via Webhook quand le paiement est effectué
- Ou consultez le status : par ID, par E2E, par Tag
Validité du QR Code
Le QR Code généré a une validité de 1 heure par défaut. La validité est configurable par compte (champ qrcode_expiration_seconds) ; vérifiez toujours le expires_at retourné dans la réponse pour la date/heure exacte.
Après expiration, le status passe à expired automatiquement (via un worker interne, routine toutes les 5 minutes). Pour les facturations annulées manuellement par le merchant, le status est cancelled. Voir la liste complète des status dans Consulter Cash-In par ID.
Cycle de vie du QR Code
Le QR Code passe par ces états après la création :
| État | Origine | Description |
|---|---|---|
active | Initial | QR généré avec succès, prêt à être scanné/payé |
paid | Paiement reçu | Paiement BACEN confirmé et lié au QR (via worker interne après ACCC) |
expired | Worker QrExpirationChecker | TTL atteint expires_at. Le worker tourne toutes les 5 min marquant les QR active expirés et déclenchant le webhook pix.charge.expired |
cancelled | Manuel ou en masse | Annulé par le merchant (via portail) ou lors d'opérations administratives (ex. : migration statique→dynamique en avril/2026 a annulé les QRs statiques actifs avec valeur) |
used | Legacy | État intermédiaire transitoire du pipeline ancien. Les nouveaux clients doivent le traiter comme équivalent à paid |
Dans les consultations, paid peut apparaître comme settled
Les endpoints GET /transactions/:id et équivalents retournent le champ status du point de vue de la transaction (pas du QR). Un QR payé apparaît comme status: "settled" dans la consultation, avec type: "pix" (ou type: "pix_qrcode" dans une courte fenêtre post-settlement tant que la ligne finale dans transactions est encore en persistance). Voir Consulter Cash-In par ID pour le tableau complet.