PIX Cash-Out via QR Code
Overview
The PIX Cash-Out via QR Code endpoint allows you to make PIX payments from a scanned or copied QR Code (copy-and-paste). The QR Code must follow the EMV standard from the Central Bank of Brazil. Recipient data is automatically extracted from the QR Code, simplifying the payment process.
This endpoint requires a valid Bearer token. See the authentication documentation for more details.
PIX Key vs QR Code: Which endpoint to use?
The Avista API offers two endpoints for sending PIX payments. Choose the most suitable one for your use case:
| Criteria | Cash-Out by PIX Key | Cash-Out via QR Code |
|---|---|---|
| Endpoint | POST /api/pix/cash-out | POST /api/pix/cash-out-qrcode |
| When to use | You know the recipient's PIX key | You have the QR Code generated by the receiver |
| Recipient data | Required (key, type, name, document) | Embedded in the QR Code (optional in request) |
| Value validation | Balance and limits only | Balance, limits + QR Code value vs provided value |
| Key types | CPF, CNPJ, email, phone, random | N/A (information inside the QR Code) |
| Confirmation webhook | CashOut event | Same CashOut event |
| Response | Same structure | Same structure |
- Use Cash-Out by Key when your application already has the recipient data (e.g., payroll, programmatic payouts)
- Use Cash-Out via QR Code when the payment is initiated from a scanned QR Code (e.g., POS, bill payment, copy-and-paste)
Both endpoints return the same response structure and trigger the same CashOut webhook when confirmed.
Features
- Payment via static or dynamic QR Code
- Automatic validation of the value embedded in the QR Code
- Automatic balance verification before sending
- Unique identification via
externalId(idempotency) - Automatic fee calculation
Endpoint
POST /api/pix/cash-out-qrcode
Makes a PIX payment from a QR Code.
Required Headers
Authorization: Bearer {token}
Content-Type: application/jsonRequest Body
{
"value": 15.50,
"qrCode": "00020126580014br.gov.bcb.pix0136a1b2c3d4-e5f6-7890-abcd-ef1234567890520400005303986540515.505802BR5925DESTINATARIO LTDA6009SAO PAULO62070503***6304ABCD",
"externalId": "QRPAY-987654-20240119",
"description": "Pagamento fornecedor XYZ via QR Code",
"name": "Destinatario Ltda",
"document": "12345678000190"
}Request
curl -X POST https://api.public.firebanking.com.br/api/pix/cash-out-qrcode \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"value": 15.50,
"qrCode": "00020126580014br.gov.bcb.pix0136a1b2c3d4-e5f6-7890-abcd-ef1234567890520400005303986540515.505802BR5925DESTINATARIO LTDA6009SAO PAULO62070503***6304ABCD",
"externalId": "QRPAY-987654-20240119",
"description": "Pagamento fornecedor XYZ via QR Code",
"name": "Destinatario Ltda",
"document": "12345678000190"
}'Response (201 Created)
{
"transactionId": "456",
"externalId": "QRPAY-987654-20240119",
"status": "PENDING",
"generateTime": "2024-01-19T14:30:00.000Z"
}Request Parameters
valuenumberobrigatorioPayment amount in BRL (Brazilian Reais). Must have at most 2 decimal places. If the QR Code contains an embedded value, the provided value must match (tolerance of 1 cent).
Minimum: 0.01
Example: 15.50
qrCodestringobrigatorioPIX QR Code content (EMV string). Can be obtained via camera scanning or from the copy-and-paste field.
Minimum: 50 characters
Maximum: 500 characters
Format: Must start with 000201 (Central Bank PIX EMV standard)
Example: "00020126580014br.gov.bcb.pix0136a1b2c3d4-e5f6-7890-abcd-ef1234567890520400005303986540515.505802BR5925DESTINATARIO LTDA6009SAO PAULO62070503***6304ABCD"
externalIdstringobrigatorioUnique external transaction identifier. Ensures idempotency -- sending the same externalId twice results in a 409 error.
Maximum: 255 characters
Recommendation: Use a format that ensures uniqueness
Example: "QRPAY-987654-20240119"
descriptionstringPayment description that will appear on the recipient's statement.
Maximum: 140 characters
Default: Empty
Example: "Pagamento fornecedor XYZ via QR Code"
namestringRecipient name. Optional -- when omitted, QR Code data is used.
Example: "Destinatario Ltda"
documentstringRecipient's CPF or CNPJ (numbers only). Optional -- when omitted, QR Code data is used.
CPF: 11 digits
CNPJ: 14 digits
Example: "12345678000190"
Response Structure
transactionIdstringsempre presenteInternal transaction ID generated by Avista.
Example: "456"
externalIdstringsempre presenteExternal ID provided in the request (same value as the input).
Example: "QRPAY-987654-20240119"
statusstringsempre presenteCurrent transaction status.
Possible values:
PENDING: Payment being processedCONFIRMED: Payment confirmed and completedERROR: Processing error
Example: "PENDING"
Note: Most PIX payments are confirmed within a few seconds
generateTimestringsempre presentePayment creation date and time (ISO 8601 UTC).
Example: "2024-01-19T14:30:00.000Z"
Implementation Examples
Node.js / TypeScript
import axios from 'axios';
interface CashOutQrCodeRequest {
value: number;
qrCode: string;
externalId: string;
description?: string;
name?: string;
document?: string;
}
interface CashOutQrCodeResponse {
transactionId: string;
externalId: string;
status: 'PENDING' | 'CONFIRMED' | 'ERROR';
generateTime: string;
}
async function payWithQrCode(
token: string,
qrCode: string,
amount: number,
description?: string
): Promise<CashOutQrCodeResponse> {
const payload: CashOutQrCodeRequest = {
value: amount,
qrCode,
externalId: `QRPAY-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
description: description || 'Pagamento via QR Code PIX'
};
try {
const response = await axios.post<CashOutQrCodeResponse>(
'https://api.public.firebanking.com.br/api/pix/cash-out-qrcode',
payload,
{
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
}
);
console.log('QR Code payment initiated!');
console.log(`Transaction ID: ${response.data.transactionId}`);
console.log(`Status: ${response.data.status}`);
console.log(`Amount: R$ ${amount.toFixed(2)}`);
return response.data;
} catch (error) {
if (axios.isAxiosError(error)) {
const errorData = error.response?.data;
console.error('Error making payment:', errorData);
if (error.response?.status === 400) {
if (errorData?.code === 'INVALID_QR_CODE') {
throw new Error('Invalid or malformed QR Code');
}
if (errorData?.code === 'QR_CODE_VALUE_MISMATCH') {
throw new Error('Provided value differs from the QR Code value');
}
if (errorData?.code === 'INSUFFICIENT_BALANCE') {
throw new Error('Insufficient balance to make the payment');
}
throw new Error('Invalid data: ' + errorData?.message);
}
if (error.response?.status === 409) {
throw new Error('externalId already used in another transaction');
}
throw new Error(errorData?.message || 'Error making QR Code payment');
}
throw error;
}
}
// Usage - Payment via scanned QR Code
const qrCodeContent = '00020126580014br.gov.bcb.pix0136a1b2c3d4...6304ABCD';
payWithQrCode('your_token_here', qrCodeContent, 15.50, 'Pagamento fornecedor');Python
import requests
from datetime import datetime
from typing import Dict, Optional
import uuid
def pay_with_qr_code(
token: str,
qr_code: str,
amount: float,
description: Optional[str] = None
) -> Dict:
"""
Send a PIX payment via QR Code
Args:
token: Valid Bearer token
qr_code: PIX QR Code content (EMV string)
amount: Amount in BRL
description: Payment description (optional)
Returns:
Initiated payment data
"""
url = 'https://api.public.firebanking.com.br/api/pix/cash-out-qrcode'
payload = {
'value': round(amount, 2),
'qrCode': qr_code,
'externalId': f'QRPAY-{int(datetime.now().timestamp())}-{uuid.uuid4().hex[:8]}',
'description': description or 'Pagamento via QR Code PIX'
}
headers = {
'Authorization': f'Bearer {token}',
'Content-Type': 'application/json'
}
try:
response = requests.post(url, json=payload, headers=headers)
response.raise_for_status()
data = response.json()
print('QR Code payment initiated!')
print(f"Transaction ID: {data['transactionId']}")
print(f"Status: {data['status']}")
print(f"Amount: R$ {amount:.2f}")
return data
except requests.exceptions.HTTPError as e:
error_data = e.response.json() if e.response else {}
if e.response.status_code == 400:
code = error_data.get('code', '')
if code == 'INVALID_QR_CODE':
raise Exception('Invalid or malformed QR Code')
if code == 'QR_CODE_VALUE_MISMATCH':
raise Exception('Provided value differs from the QR Code value')
if code == 'INSUFFICIENT_BALANCE':
raise Exception('Insufficient balance to make the payment')
raise Exception(f"Invalid data: {error_data.get('message')}")
if e.response.status_code == 409:
raise Exception('externalId already used in another transaction')
raise Exception(f"Error making payment: {error_data.get('message', str(e))}")
# Usage
token = 'your_token_here'
qr_code = '00020126580014br.gov.bcb.pix0136a1b2c3d4...6304ABCD'
payment = pay_with_qr_code(
token=token,
qr_code=qr_code,
amount=15.50,
description='Pagamento fornecedor XYZ via QR Code'
)PHP
<?php
function payWithQrCode(
string $token,
string $qrCode,
float $amount,
?string $description = null
): array {
$url = 'https://api.public.firebanking.com.br/api/pix/cash-out-qrcode';
$payload = [
'value' => round($amount, 2),
'qrCode' => $qrCode,
'externalId' => 'QRPAY-' . time() . '-' . bin2hex(random_bytes(4)),
'description' => $description ?? 'Pagamento via QR Code PIX'
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $token,
'Content-Type: application/json'
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 201) {
$errorData = json_decode($response, true);
$errorCode = $errorData['code'] ?? '';
$errorMessage = $errorData['message'] ?? "HTTP $httpCode";
if ($httpCode === 400) {
if ($errorCode === 'INVALID_QR_CODE') {
throw new Exception('Invalid or malformed QR Code');
}
if ($errorCode === 'QR_CODE_VALUE_MISMATCH') {
throw new Exception('Provided value differs from the QR Code value');
}
if ($errorCode === 'INSUFFICIENT_BALANCE') {
throw new Exception('Insufficient balance to make the payment');
}
}
if ($httpCode === 409) {
throw new Exception('externalId already used in another transaction');
}
throw new Exception("Error making payment: $errorMessage");
}
$data = json_decode($response, true);
echo "QR Code payment initiated!" . PHP_EOL;
echo "Transaction ID: {$data['transactionId']}" . PHP_EOL;
echo "Status: {$data['status']}" . PHP_EOL;
echo "Amount: R$ " . number_format($amount, 2, ',', '.') . PHP_EOL;
return $data;
}
// Usage
$token = 'your_token_here';
$qrCode = '00020126580014br.gov.bcb.pix0136a1b2c3d4...6304ABCD';
$payment = payWithQrCode($token, $qrCode, 15.50, 'Pagamento fornecedor XYZ');Use Cases
1. POS -- Pay via QR Code at Checkout
class PointOfSalePayment {
constructor(private token: string) {}
async payFromScannedQrCode(qrCodeContent: string, amount: number) {
// Validate QR Code locally before sending
if (!this.isValidPixQrCode(qrCodeContent)) {
throw new Error('Invalid QR Code. Please verify and try again.');
}
const payment = await payWithQrCode(
this.token,
qrCodeContent,
amount,
`Pagamento PDV - ${new Date().toLocaleDateString('pt-BR')}`
);
console.log(`Payment initiated: ${payment.transactionId}`);
return payment;
}
private isValidPixQrCode(qrCode: string): boolean {
return qrCode.length >= 50 && qrCode.startsWith('000201');
}
}2. Supplier Payment via QR Code
class SupplierPayment:
"""Processes supplier payments via QR Code"""
def __init__(self, token: str):
self.token = token
def pay_supplier_invoice(self, qr_code: str, invoice_amount: float, invoice_id: str):
"""Pays a supplier invoice via QR Code"""
# Check balance first
balance = get_balance(self.token)
if balance['netBalance'] < invoice_amount:
raise Exception(f"Insufficient balance. Available: R$ {balance['netBalance']:.2f}")
payment = pay_with_qr_code(
token=self.token,
qr_code=qr_code,
amount=invoice_amount,
description=f'Pagamento fatura #{invoice_id}'
)
return {
'invoice_id': invoice_id,
'transaction_id': payment['transactionId'],
'status': payment['status'],
'amount': invoice_amount
}3. Recurring Payment Automation
class RecurringQrCodePayment {
constructor(token) {
this.token = token;
}
async processPaymentBatch(payments) {
const results = { successful: [], failed: [] };
for (const payment of payments) {
try {
const result = await payWithQrCode(
this.token,
payment.qrCode,
payment.amount,
payment.description
);
results.successful.push({
reference: payment.reference,
transactionId: result.transactionId,
amount: payment.amount
});
// Wait between payments
await new Promise(resolve => setTimeout(resolve, 1000));
} catch (error) {
results.failed.push({
reference: payment.reference,
error: error.message
});
}
}
return results;
}
}QR Code Validation
The PIX QR Code follows the EMV (Europay, Mastercard, Visa) standard defined by the Central Bank of Brazil. Before sending it to the API, you can validate it locally:
function isValidPixQrCode(qrCode: string): boolean {
// Check minimum and maximum length
if (qrCode.length < 50 || qrCode.length > 500) {
return false;
}
// Check mandatory EMV prefix
if (!qrCode.startsWith('000201')) {
return false;
}
return true;
}EMV PIX QR Code structure:
000201-- Payload Format Indicator (mandatory)0102XX-- Point of Initiation Method (11= static,12= dynamic)- Fields with receiver data, value, city, etc.
6304XXXX-- CRC16 (validation checksum)
Full QR Code validation (EMV decoding, CRC verification, and data extraction) is done automatically by the API. Local validation serves only to filter clearly invalid QR Codes.
Response Codes
| Code | Error | Description |
|---|---|---|
201 | -- | PIX payment via QR Code initiated successfully |
400 | INVALID_QR_CODE | Invalid or malformed QR Code |
400 | QR_CODE_VALUE_MISMATCH | Provided value differs from the value embedded in the QR Code |
400 | INSUFFICIENT_BALANCE | Insufficient balance to complete the transaction |
401 | -- | Token not provided, expired, or invalid |
409 | DUPLICATE_EXTERNAL_ID | externalId already used in another transaction |
See the API Reference for full response field details.
Best Practices
Important Notes
- Minimum amount: R$ 0.01
- QR Code format: Must start with
000201and be between 50 and 500 characters - Dynamic QR Codes: QR Codes without an embedded value are accepted -- the
valuefield defines the payment amount - Static QR Codes with value: The value provided in
valuemust match the value embedded in the QR Code (tolerance of 1 cent)