Pular para o conteúdo
xFlow Payments

Blog

API Pix em Python

Para integrar a API Pix em Python, envie POST /api/v1/charges com requests e auth=(pk_live_, sk_live_), mostre o pix.qrCodeBase64 e o pix.copyPaste da resposta e confirme o pagamento pelo webhook transaction.paid, validando o HMAC-SHA256 do corpo cru com hmac.compare_digest. No sandbox, use chaves pk_test_ e simule o pagamento.

Atualizado em · Equipe xFlow Payments · 7 min de leitura

Em Python, a API Pix se integra com a biblioteca requests e poucas linhas: um POST cria a cobrança, a resposta já traz o QR Code e o copia e cola, e uma rota do seu servidor recebe o webhook de pagamento. O código abaixo segue a documentação da API, roda em Python 3.9 ou superior e usa a base https://app.xflowpayments.com/api/v1. Para entender a integração completa antes do código, leia o guia da API Pix.

Antes de começar

  • Gere uma chave em Desenvolvedores > Chaves de API, no painel. A chave pública (pk_live_...) fica sempre visível. O segredo (sk_live_...) aparece uma única vez.
  • Para testar, crie uma chave do ambiente Teste (pk_test_... e sk_test_...). Ela pode ser criada antes da aprovação do cadastro.
  • Instale a dependência: pip install requests.
  • Defina XFLOW_PUBLIC_KEY, XFLOW_SECRET_KEY e XFLOW_WEBHOOK_SECRET como variáveis de ambiente.

O segredo fica no servidor. Não o coloque em código versionado nem em nada que rode no navegador.

Um cliente para a API

A autenticação é HTTP Basic: usuário é a chave pública, senha é o segredo. Com requests, basta auth=(publica, segredo). Valores monetários são inteiros em centavos.

python
# xflow.py
import os
import requests

BASE = "https://app.xflowpayments.com/api/v1"
AUTH = (os.environ["XFLOW_PUBLIC_KEY"], os.environ["XFLOW_SECRET_KEY"])


class XflowErro(Exception):
    def __init__(self, status, corpo, retry_after=None):
        self.status = status
        self.code = corpo.get("error")
        self.request_id = corpo.get("request_id")
        self.retry_after = retry_after
        super().__init__(f"{status} {self.code}: {corpo.get('message')} ({self.request_id})")


def xflow(method, path, json=None, idempotency_key=None):
    headers = {"Idempotency-Key": idempotency_key} if idempotency_key else {}
    resp = requests.request(method, BASE + path, auth=AUTH, json=json, headers=headers, timeout=30)
    corpo = resp.json() if resp.content else {}
    if resp.status_code >= 400:
        raise XflowErro(resp.status_code, corpo, resp.headers.get("retry-after"))
    return corpo

Sem dependências, com urllib

Se não puder instalar pacotes, a biblioteca padrão resolve. A diferença é montar o header Authorization à mão:

python
import base64
import json
import os
import urllib.request

cred = f"{os.environ['XFLOW_PUBLIC_KEY']}:{os.environ['XFLOW_SECRET_KEY']}".encode()
req = urllib.request.Request(
    "https://app.xflowpayments.com/api/v1/balance",
    headers={"Authorization": "Basic " + base64.b64encode(cred).decode()},
)
with urllib.request.urlopen(req, timeout=30) as resp:
    print(json.load(resp))  # {"available": ..., "reserved": ..., "currency": "BRL"}

Com urllib, uma resposta de erro vira a exceção urllib.error.HTTPError. O corpo dela, lido com .read(), traz o mesmo JSON de erro da API.

Toda resposta traz um request_id (req_...), no corpo e no header x-request-id. Registre esse valor quando der erro: ele localiza a chamada em Integrações > Logs da API, onde cada requisição autenticada fica 30 dias com status, duração e código de erro. A tabela de códigos está em erros da API Pix.

Criar a cobrança Pix

python
from xflow import xflow

pedido_id = "1234"

cobranca = xflow(
    "POST",
    "/charges",
    json={
        "amountCents": 1990,  # R$ 19,90
        "customer": {
            "name": "Cliente Exemplo",
            "email": "cliente@exemplo.com",
            "document": "00000000000",  # CPF ou CNPJ, só dígitos
        },
        "description": f"Pedido #{pedido_id}",
        "external_reference": f"pedido-{pedido_id}",
    },
    idempotency_key=f"pedido-{pedido_id}",
)

print(cobranca["id"], cobranca["status"])  # clx... pending

A Idempotency-Key deve ser única por pedido. Se a conexão cair e você repetir a chamada com a mesma chave, a API devolve a mesma cobrança em vez de gerar outro Pix. Com a mesma chave e outro corpo, a resposta é 409 idempotency_conflict. Veja o porquê em idempotência em API de pagamento.

Salve o id devolvido junto do pedido. Ele serve para consultar o status em GET /api/v1/charges/{id} e, se o cliente desistir, para pedir a devolução em POST /api/v1/charges/{id}/refund, detalhada em estorno Pix por API.

O external_reference (até 120 caracteres) liga a cobrança ao seu pedido. Ele volta nas respostas e webhooks e filtra a listagem: GET /api/v1/charges?external_reference=pedido-1234. Valor fora do mínimo ou do máximo da conta retorna 400 validation.

Mostrar o QR Code e o copia e cola

Em um template Jinja, por exemplo:

html
<img src="{{ cobranca.pix.qrCodeBase64 }}" alt="QR Code Pix" width="240" height="240">
<textarea readonly>{{ cobranca.pix.copyPaste }}</textarea>

pix.qrCodeBase64 é um PNG em data-URL, pronto para o src. pix.copyPaste é o payload BR Code que o pagador cola no app do banco. Repasse sem alterar. Os dois campos vêm na criação e em GET /api/v1/charges/{id}, mas não na listagem. Para exibição em outros canais, veja QR Code Pix dinâmico por API.

Receber o webhook com Flask

Cadastre a URL em Integrações > Webhooks. Ela tem que ser https:// e pública. Cada entrega é um POST JSON com os headers x-webhook-event e x-webhook-signature: sha256=..., o HMAC-SHA256 do corpo cru com o segredo do endpoint.

python
# webhook.py
import hashlib
import hmac
import json
import os

from flask import Flask, abort, request

app = Flask(__name__)
SEGREDO = os.environ["XFLOW_WEBHOOK_SECRET"].encode()


def assinatura_valida(raw, header):
    esperado = "sha256=" + hmac.new(SEGREDO, raw, hashlib.sha256).hexdigest()
    return hmac.compare_digest(esperado.encode(), (header or "").encode())


@app.post("/webhooks/xflow")
def webhook():
    raw = request.get_data()  # corpo cru, antes de qualquer parse
    if not assinatura_valida(raw, request.headers.get("x-webhook-signature")):
        abort(401)

    evento = json.loads(raw)
    data = evento["data"]
    try:
        if evento["event"] == "transaction.paid" and data["livemode"]:
            dar_baixa_no_pedido(data["external_reference"], data["pix"]["endToEnd"])  # sua função
    except Exception:
        return "", 500  # sem 2xx, a entrega é reenviada
    return "", 200

No FastAPI, o corpo cru vem de await request.body(). O resto é igual. Os cuidados:

  • Valide antes do json.loads. Usar request.get_json() e serializar de novo muda os bytes e quebra o HMAC.
  • Compare bytes com hmac.compare_digest, que não vaza tempo de comparação.
  • Responda 2xx. Sem isso, a xFlow reenvia até 3 vezes. A sua baixa precisa tolerar o mesmo evento duas vezes.
  • Descarte livemode: false em produção: são eventos gerados com chave de teste.

No webhook, o valor vem em amount, e transaction.paid traz também net e fee. payer e pix.endToEnd vêm do PSP na confirmação, para conciliar com o extrato. Todos os eventos estão em webhook Pix.

Testar no sandbox

Com as chaves de teste nas variáveis, o mesmo código roda sem mover dinheiro. Toda resposta traz livemode: false, nenhum provedor Pix é chamado e o pix.copyPaste é um marcador que não pode ser pago.

python
from xflow import xflow

teste = xflow("POST", "/charges", json={
    "amountCents": 1990,
    "customer": {"name": "Cliente", "email": "c@exemplo.com", "document": "00000000000"},
})

xflow("POST", f"/test/charges/{teste['id']}/pay")

paga = xflow("GET", f"/charges/{teste['id']}")
print(paga["status"], paga["livemode"])  # paid False

A cobrança vira paid, a taxa é calculada com as taxas reais da sua conta e o webhook transaction.paid é disparado com livemode: false. A simulação só aceita chave de teste. Com chave de produção, volta 403 test_mode_only. Saques não rodam em teste (403 test_mode_unsupported). O ambiente está detalhado em sandbox da API Pix.

Conciliar as cobranças pagas

Para fechar o dia, liste as cobranças pagas de um período. A paginação é por cursor: limit vai de 1 a 100 e, enquanto has_more for verdadeiro, você repete a chamada com starting_after igual ao id do último item.

python
from urllib.parse import urlencode
from xflow import xflow


def cobrancas_pagas(de, ate):
    cursor = None
    while True:
        filtros = {"status": "paid", "from": de, "to": ate, "limit": 100}
        if cursor:
            filtros["starting_after"] = cursor
        pagina = xflow("GET", "/charges?" + urlencode(filtros))
        yield from pagina["data"]
        if not pagina["has_more"] or not pagina["data"]:
            break
        cursor = pagina["data"][-1]["id"]


for c in cobrancas_pagas("2026-10-01", "2026-10-31"):
    print(c["external_reference"], c["amountCents"])

Cada item traz payer e pix.endToEnd, que a documentação indica para conciliar o recebimento com o extrato bancário.

Antes de ir para produção

  1. Troque as chaves de teste pelas de produção (pk_live_/sk_live_) só no ambiente de produção.
  2. Cadastre a URL de produção do webhook e guarde o segredo do endpoint.
  3. Mantenha o descarte de eventos com livemode: false.
  4. Registre o request_id de toda resposta com erro.
  5. Se quiser restringir a origem das chamadas, ative a allowlist de IP em Chaves de API. Fora da lista, a resposta é 401.

Limite de requisições e retentativa

Um 429 rate_limited traz o header retry-after, em segundos. Espere esse tempo e tente de novo com backoff:

python
import time
from xflow import XflowErro, xflow


def com_retentativa(method, path, tentativas=3, **kwargs):
    for tentativa in range(tentativas):
        try:
            return xflow(method, path, **kwargs)
        except XflowErro as erro:
            if erro.status != 429 or tentativa == tentativas - 1:
                raise
            time.sleep(int(erro.retry_after or 2 ** tentativa))

O limite é por conta. Toda resposta autenticada traz x-ratelimit-limit, x-ratelimit-remaining e x-ratelimit-reset, então dá para desacelerar antes de tomar o 429. Prefira o webhook ao polling: consultar GET /api/v1/charges/{id} em laço gasta o mesmo limite. O fluxo em outras linguagens está em API Pix em Node.js e API Pix em PHP.

Perguntas frequentes

Qual biblioteca usar para a API Pix em Python?

A requests resolve as chamadas: auth=(chave_publica, segredo) já monta o HTTP Basic que a API da xFlow pede. Se preferir não instalar nada, o urllib.request da biblioteca padrão também funciona, com o header Authorization montado à mão.

Como validar o webhook Pix em Flask ou FastAPI?

Leia o corpo cru (request.get_data() no Flask, await request.body() no FastAPI), calcule o HMAC-SHA256 com o segredo do endpoint, prefixe com sha256= e compare com o header x-webhook-signature usando hmac.compare_digest. Só depois converta o JSON.

Como simular um pagamento Pix em Python?

Com chaves de teste (pk_test_/sk_test_), crie a cobrança e chame POST /api/v1/test/charges/{id}/pay. A cobrança vira paid e o webhook transaction.paid chega com livemode: false.

Posso mandar o valor como float, por exemplo 19.90?

Não. Os valores são inteiros em centavos: R$ 19,90 vai como amountCents: 1990. Isso também evita erro de arredondamento com float.

Continue lendo

SoluçãoAPI PixA API Pix da xFlow Payments é uma API REST para criar cobranças Pix com QR Code e copia e cola, estornar, sacar e receber webhooks assinados. Uma única integração conecta a sua operação a 11 adquirentes, com roteamento por regras e retentativa automática.BlogWebhook Pix: eventos, assinatura HMAC e boas práticasWebhook Pix é o aviso que a API envia para a sua URL quando algo acontece com uma cobrança ou um saque, como o pagamento confirmado. Na xFlow, cada aviso é um POST JSON assinado com HMAC-SHA256, reenviado até 3 vezes até você responder 2xx.BlogAPI Pix sandbox: como testar sem mover dinheiroO sandbox da API Pix da xFlow funciona com chaves pk_test_ e sk_test_: mesmos endpoints, mesmas respostas com livemode: false, nenhum dinheiro real e um endpoint para simular o pagamento que dispara o webhook transaction.paid.BlogErros da API PixNa API Pix da xFlow, todo erro volta em JSON com error (o código), message e request_id. O status HTTP diz a família do problema: 400 corpo inválido, 401 credencial, 403 conta ou modo, 404 recurso inexistente, 409 idempotência, 422 recusa de negócio, 429 limite e 502 falha no provedor. O request_id localiza a chamada em Integrações > Logs da API.BlogAPI Pix em Node.jsPara integrar a API Pix em Node.js, chame POST /api/v1/charges com fetch e Basic auth (pk_live_ como usuário, sk_live_ como senha), mostre o pix.qrCodeBase64 e o pix.copyPaste da resposta e confirme o pagamento pelo webhook transaction.paid, validando a assinatura HMAC-SHA256 do corpo cru. Antes da produção, teste com chaves pk_test_ e simule o pagamento.