Integração de Pagamentos com PIX em Python: Guia Prático
Aprenda a integrar pagamentos por PIX em Python: criar cobrança com QR Code e Copia e Cola, receber webhooks com FastAPI, validar assinatura, garantir idempotência e segurança.
Integrar pagamentos por PIX em Python é hoje uma das habilidades mais pedidas em vagas de backend e fintech no Brasil. Desde 2020, o PIX virou o meio de pagamento padrão para transferências imediatas, e quase todo sistema que vende algo — e-commerce, SaaS, assinatura, marketplace, doação — precisa gerar uma cobrança, mostrar um QR Code e confirmar o pagamento de forma automática.
A boa notícia é que a integração não é difícil do ponto de vista de código: o fluxo gira em torno de chamadas HTTP, webhooks e uma chave de idempotência. O desafio real está nos detalhes que importam em produção — validar a assinatura do webhook, evitar cobrança duplicada, tratar concorrência, guardar segredos de forma segura e conciliar o que foi pago com o que foi prometido. Neste guia vamos montar esse fluxo inteiro em Python, com FastAPI para o webhook e boas práticas que valem para qualquer PSP (provedor de serviço de pagamento).
Como o PIX funciona para quem integra
Do ponto de vista técnico, o fluxo de uma cobrança PIX tem quatro peças:
- Cobrança: você cria uma cobrança no PSP e recebe um
txid(identificador único da transação). - BR Code / Copia e Cola: o PSP devolve uma string no padrão EMV — o famoso “PIX Copia e Cola” — que o cliente cola no app do banco.
- QR Code: a mesma string pode virar uma imagem de QR Code, lida pela câmera do celular.
- Webhook de confirmação: quando o cliente paga, o PSP chama o seu endpoint avisando que o
txidfoi liquidado.
O detalhe que confunde muita gente: a confirmação nunca vem como resposta direta da criação da cobrança. A criação só reserva a cobrança; o dinheiro chega depois, de forma assíncrona, via webhook. Por isso, tratar webhook com idempotência é a parte mais importante de toda a integração.
Banco Central direto ou PSP?
Você pode integrar o PIX de duas formas. A escolha muda bastante o nível de esforço:
| Caminho | Quando usar | Exige |
|---|---|---|
| Banco Central direto (API Pix) | Instituições financeiras, fintechs com conta institucional | Certificado ICP-Brasil, mTLS, conta regulada pelo BCB |
| PSP (Mercado Pago, Asaas, Gerencianet, Pagar.me) | E-commerce, SaaS, startups, qualquer empresa | Conta no PSP, access_token, HTTPS no webhook |
Para a esmagadora maioria dos projetos em Python, o caminho é o PSP. Ele abstrai o certificado, o mTLS e a comunicação direta com o Bacen, e expõe uma API REST simples com HTTPS. Vamos seguir por aqui.
Preparando o ambiente
Comece com um ambiente isolado, como já mostramos no guia de ambientes virtuais em Python. Com uv, o setup é rápido:
uv init pix-python
cd pix-python
uv add fastapi "uvicorn[standard]" httpx pydantic qrcode python-dotenv
Use HTTPX como cliente HTTP: ele suporta async, timeouts configuráveis e retries, fundamentais para não travar quando o PSP demora a responder. Para guardar chaves de API com segurança, leia o guia de variáveis de ambiente com python-dotenv — nunca coloque access_token no código.
Criando uma cobrança com QR Code e Copia e Cola
A estrutura de uma cobrança é parecida entre os PSPs. O exemplo abaixo usa um cliente HTTPX genérico que funciona com a maioria das APIs. Ajuste o base_url e o corpo para o PSP que você escolher (Mercado Pago, Asaas, Gerencianet e Pagar.me seguem o mesmo padrão geral):
import os
import httpx
from dataclasses import dataclass
PSP_BASE_URL = os.environ["PSP_BASE_URL"] # ex.: https://api.asaas.com ou https://api.mercadopago.com
PSP_TOKEN = os.environ["PSP_TOKEN"] # access_token do PSP
@dataclass
class Cobranca:
txid: str
copia_e_cola: str # string "PIX Copia e Cola" (BR Code)
qr_code_base64: str # imagem do QR Code em base64
async def criar_cobranca(cliente_id: str, valor: float, descricao: str) -> Cobranca:
headers = {"Authorization": f"Bearer {PSP_TOKEN}", "Content-Type": "application/json"}
payload = {
"customer": cliente_id,
"billingType": "PIX",
"value": valor,
"description": descricao,
}
async with httpx.AsyncClient(timeout=10.0) as client:
resp = await client.post(f"{PSP_BASE_URL}/payments", json=payload, headers=headers)
resp.raise_for_status()
data = resp.json()
# Cada PSP nomeia os campos de forma ligeiramente diferente:
# Asaas: payload (Copia e Cola), encodedImage (QR Code base64)
# Mercado Pago: qr_code / ticket_url + qr_code_base64
return Cobranca(
txid=data["id"],
copia_e_cola=data.get("payload") or data.get("qr_code"),
qr_code_base64=data.get("encodedImage") or data.get("qr_code_base64", ""),
)
Salve o txid no banco associado ao pedido. Ele é a sua chave de idempotência: qualquer evento futuro (webhook, consulta de status, reembolso) referencia esse mesmo identificador.
Para gerar o QR Code localmente a partir do Copia e Cola (útil quando o PSP só devolve a string), use a biblioteca qrcode:
import qrcode
def salvar_qrcode(copia_e_cola: str, caminho: str) -> None:
img = qrcode.make(copia_e_cola)
img.save(caminho)
Recebendo o webhook de confirmação com FastAPI
A peça central da integração é o endpoint que recebe a notificação de pagamento. O PSP chama esse URL sempre que uma cobrança é liquidada — e pode chamar mais de uma vez para a mesma transação (retry em falha de rede). Por isso, idempotência é obrigatória.
O exemplo abaixo usa FastAPI e segue o mesmo padrão de robustez que mostramos no guia de webhooks com FastAPI:
from fastapi import FastAPI, Request, HTTPException, BackgroundTasks
from pydantic import BaseModel
app = FastAPI()
# status por txid — na prática, um banco de dados
status_pedidos: dict[str, str] = {}
class NotificacaoPix(BaseModel):
event: str
payment: dict | None = None
@app.post("/webhook/pix")
async def webhook_pix(req: Request, background: BackgroundTasks):
# 1. Validar assinatura antes de confiar no conteúdo
if not assinatura_valida(req):
raise HTTPException(status_code=401, detail="Assinatura inválida")
body = await req.json()
txid = body.get("payment", {}).get("id")
if not txid:
raise HTTPException(status_code=400, detail="txid ausente")
# 2. Idempotência: ignorar se já processado
if status_pedidos.get(txid) == "paid":
return {"status": "ok", "duplicado": True}
# 3. Confirmar de forma resiliente, fora do ciclo do webhook
background.add_task(confirmar_pagamento, txid)
return {"status": "ok"}
async def confirmar_pagamento(txid: str) -> None:
# Consulte o PSP para confirmar o status real (não confie só no webhook)
status = await consultar_status_no_psp(txid)
if status == "paid":
status_pedidos[txid] = "paid"
# aqui: liberar pedido, enviar e-mail, atualizar estoque...
Do pontos críticos desse código:
- Sempre valide a assinatura do webhook (header de assinatura/HMAC enviado pelo PSP). Sem isso, qualquer um pode simular um pagamento pago.
- Sempre confirme consultando o PSP antes de liberar o pedido. O webhook avisa; a consulta confirma. Isso protege contra webhooks forjados e contra falsos positivos.
- Processe de forma assíncrona (
BackgroundTasksou uma fila). O webhook precisa responder rápido (200 OK), senão o PSP faz retry e você processa o mesmo evento várias vezes.
Segurança que não pode faltar
Integração financeira é segurança aplicada. Some pontos não negociáveis:
- Chaves em variáveis de ambiente, nunca no código nem no Git. Use
python-dotenvlocal e o cofre de segredos do provedor (AWS Secrets Manager, Cloudflare, etc.) em produção. - Valide a assinatura de todo webhook. Cada PSP documenta qual header carrega a assinatura e qual segredo usar no HMAC.
- Não registre dados sensíveis em log: CPF, e-mail, número de cartão, chave PIX de terceiros. O próprio Bacen recomenda anonimizar. O guia de conciliação financeira com Python mostra como usar dados sintéticos em testes para nunca vazar informação real.
- Use HTTPS em todo o caminho, do PSP até o seu banco de dados.
Conciliação, testes e erros comuns
Mesmo com webhook perfeito, você ainda precisa conciliar periodicamente: comparar o que o PSP diz que foi pago com o que o seu sistema registrou. PIX pode cair no mesmo dia, mas aparecer com descrição diferente; webhook pode se perder; cliente pode pagar após o vencimento. Esse casamento de dados é exatamente o que cobrimos no guia de conciliação financeira — leia em conjunto.
Para testar, cubra o fluxo inteiro com pytest: criação de cobrança, tratamento de webhook válido, webhook duplicado (idempotência) e assinatura inválida (rejeição). Os PSPs oferecem ambientes de sandbox com chaves de teste — use-os sempre.
Erros comuns que valem o checklist final:
- confiar só no webhook sem consultar o PSP (falso positivo);
- não tratar
txidduplicado (cobrança processada duas vezes); - responder o webhook com lentidão (PSP dispara retries);
- deixar o
access_tokenno repositório (vazamento de credencial); - não validar a assinatura (qualquer pessoa simula pagamento).
Portfólio e carreira
Uma integração de PIX ponta a ponta — cobrança, QR Code, webhook com idempotência, validação de assinatura e conciliação — é um dos projetos mais fortes que você pode colocar em um portfólio para vagas de backend Python. Ela mostra domínio de APIs assíncronas, segurança aplicada, modelagem de dados e atenção a detalhes de produção. Se quiser montar isso como peça de portfólio, o guia de projetos de portfólio em Python traz a estrutura certa, e o roadmap Python 2026 mostra onde pagamentos se encaixam na trilha de carreira.
Vagas de fintech e backend frequentemente listam “integração de meios de pagamento” como requisito — confira as oportunidades na nossa página de vagas de Python em fintech. Dominar esse fluxo coloca você à frente de boa parte dos candidatos.
Conclusão
Integrar PIX em Python é, no fundo, um exercício de disciplina: HTTPX para criar a cobrança, QR Code para o cliente, webhook com FastAPI para confirmar, idempotência por txid para evitar duplicidade e validação de assinatura para segurança. O código é curto; o que sustenta a integração em produção são as decisões de idempotência, conciliação e proteção de dados. Comece com um PSP em sandbox, monte o fluxo completo ponta a ponta e adicione testes e conciliação antes de qualquer coisa ir ao ar.
Perguntas frequentes
Preciso de certificado digital para integrar PIX em Python?
Depende do caminho. A integração direta com o Banco Central (API Pix) exige conta institucional e certificado ICP-Brasil com mTLS, o que não se aplica a pessoas físicas ou empresas comuns. Integrar via PSP (Mercado Pago, Asaas, Gerencianet, Pagar.me) usa apenas HTTPS e access_token, sem certificado.
Como evito processar o mesmo pagamento duas vezes?
Use o txid da cobrança como chave de idempotência e persista o status do pagamento no banco antes de atualizar o pedido. Se o webhook chegar repetido, a segunda ocorrência encontra o status já confirmado e é ignorada.
Qual biblioteca Python uso para gerar o QR Code do PIX?
A maioria dos PSPs já retorna a string Copia e Cola e o QR Code em base64 na resposta da cobrança, então você apenas salva a imagem. Para gerar o BR Code localmente, a biblioteca qrcode do Python monta a imagem a partir do payload.
Posso receber webhook de PIX em produção sem domínio HTTPS?
Não. O PSP exige um endpoint público com HTTPS válido para entregar a notificação. Em desenvolvimento, use um túnel como ngrok ou Cloudflare Tunnel para expor o FastAPI local.
Equipe Python Brasil
Contribuidor do Python Brasil — Aprenda Python em Português