NTX Paydocs
Integration Guides

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:

CriteriaCash-Out by PIX KeyCash-Out via QR Code
EndpointPOST /api/pix/cash-outPOST /api/pix/cash-out-qrcode
When to useYou know the recipient's PIX keyYou have the QR Code generated by the receiver
Recipient dataRequired (key, type, name, document)Embedded in the QR Code (optional in request)
Value validationBalance and limits onlyBalance, limits + QR Code value vs provided value
Key typesCPF, CNPJ, email, phone, randomN/A (information inside the QR Code)
Confirmation webhookCashOut eventSame CashOut event
ResponseSame structureSame 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/json

Request 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.ntxpay.com/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

valuenumberobrigatorio

Payment 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

qrCodestringobrigatorio

PIX 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"

externalIdstringobrigatorio

Unique 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"

descriptionstring

Payment description that will appear on the recipient's statement.

Maximum: 140 characters

Default: Empty

Example: "Pagamento fornecedor XYZ via QR Code"

namestring

Recipient name. Optional -- when omitted, QR Code data is used.

Example: "Destinatario Ltda"

documentstring

Recipient'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 presente

Internal transaction ID generated by Avista.

Example: "456"

externalIdstringsempre presente

External ID provided in the request (same value as the input).

Example: "QRPAY-987654-20240119"

statusstringsempre presente

Current transaction status.

Possible values:

  • PENDING: Payment being processed
  • CONFIRMED: Payment confirmed and completed
  • ERROR: Processing error

Example: "PENDING"

Note: Most PIX payments are confirmed within a few seconds

generateTimestringsempre presente

Payment 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.ntxpay.com/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.ntxpay.com/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.ntxpay.com/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

CodeErrorDescription
201--PIX payment via QR Code initiated successfully
400INVALID_QR_CODEInvalid or malformed QR Code
400QR_CODE_VALUE_MISMATCHProvided value differs from the value embedded in the QR Code
400INSUFFICIENT_BALANCEInsufficient balance to complete the transaction
401--Token not provided, expired, or invalid
409DUPLICATE_EXTERNAL_IDexternalId 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 000201 and be between 50 and 500 characters
  • Dynamic QR Codes: QR Codes without an embedded value are accepted -- the value field defines the payment amount
  • Static QR Codes with value: The value provided in value must match the value embedded in the QR Code (tolerance of 1 cent)

Next Steps

On this page