Multi-tenant com virtualAccount
Uma conta PayZu só atende várias lojas, filiais ou marcas: marque cada transação com o identificador do tenant e ele volta em todo callback e serve de filtro nas listagens, sem precisar abrir contas separadas.
Se você opera várias marcas, lojas, filiais ou parceiros em uma só conta PayZu, passe virtualAccount (até 50 caracteres) em cada criação. Esse campo:
- Volta em todo callback, você sabe imediatamente de qual tenant é a transação.
- Pode ser filtrado em
GET /user/transactionseGET /pix, listas isoladas por tenant. - Dispensa contas filhas, uma só conta PayZu serve N tenants.
Convenções de nome
| Padrão | Quando usar |
|---|---|
tenant-{slug} | Plataforma SaaS multi-cliente. |
loja-{cidade}-{numero} | Rede com lojas físicas. |
mkt-{partner} | Marketplace com vários vendedores. |
filial-{codigo} | Filiais de uma mesma empresa. |
branch-{branchId} | Genérico, em inglês. |
Tamanho máximo: 50 caracteres. Use formato estável e legível. Evite caracteres especiais e espaços.
Criar com virtualAccount
{
"amount": 99.90,
"clientReference": "order-1234",
"virtualAccount": "loja-rj-01",
"callbackUrl": "https://seusite.com.br/webhooks/payzu"
}{
"id": "PAYZU20251123104518DF75D20A8F",
"status": "PENDING",
"amount": 99.90,
"clientReference": "order-1234",
"virtualAccount": "loja-rj-01",
"qrCodeText": "00020126870014br.gov.bcb.pix..."
}{
"id": "PAYZU20251123104518DF75D20A8F",
"type": "DEPOSIT",
"status": "COMPLETED",
"amount": 99.90,
"clientReference": "order-1234",
"virtualAccount": "loja-rj-01",
"paidAt": "2025-11-23T10:46:26.986Z"
}Listar só de um tenant
curl "https://api.payzu.processamento.com/v1/user/transactions?virtualAccount=loja-rj-01&dateFrom=2025-11-01" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json"const url = new URL('https://api.payzu.processamento.com/v1/user/transactions');
url.searchParams.set('virtualAccount', 'loja-rj-01');
url.searchParams.set('dateFrom', '2025-11-01');
const res = await fetch(url, {
headers: {
Authorization: `Bearer ${process.env.PAYZU_TOKEN}`,
'Content-Type': 'application/json',
},
});Rotear o callback
async function payzuWebhook(tx: PayzuCallback) {
const tenantId = tx.virtualAccount;
if (!tenantId) {
log.warn('callback sem virtualAccount', { id: tx.id });
return;
}
const handler = tenantHandlers[tenantId];
if (!handler) {
log.error('tenant desconhecido', { tenantId, id: tx.id });
return;
}
await handler.process(tx);
}virtualAccount vs clientReference
Os dois são campos independentes e complementares. Use os dois sempre.
| Campo | Granularidade | Propósito |
|---|---|---|
clientReference | Por transação | Idempotência + lookup por pedido. |
virtualAccount | Por tenant | Roteamento + filtro de listagens. |
Basta enviar os dois campos no mesmo payload de criação, como no exemplo acima.
Armadilhas comuns
| Armadilha | Sintoma |
|---|---|
Usar clientReference pra identificar tenant | Não filtra em listagens, complica lookup |
virtualAccount muda toda vez (timestamp, slug variável) | Listagem fica fragmentada |
Não tratar callback sem virtualAccount | Roteamento crasha em transações legadas |
| Hardcode de tenants no handler | Onboarding manual a cada novo cliente |
Idempotência
Chamar a mesma operação de novo não deveria cobrar duas vezes nem dar baixa em dobro: veja como usar a sua referência para amarrar isso e por que os callbacks chegam repetidos.
Paginação
Percorra listas grandes de transações, callbacks e infrações página por página, sabendo pela resposta se ainda há mais; para janelas longas, prefira o relatório assíncrono em CSV.