PayZuPayZu Docs
Boas práticas

Tratamento de erros

A PayZu retorna os códigos HTTP padrão. Sua estratégia depende da categoria.

Tabela de tratamento

CódigoO que significaO que fazer
200Sucesso na operação.Processar resposta.
201Recurso criado (cobrança, saque).Processar resposta.
400Payload inválido.Não retentar. Logue message + requestId e ajuste o código.
401Token ausente, inválido ou revogado.Não retentar. Verifique o token (espaço, encoding, rotação).
403Token válido mas sem permissão para o endpoint.Não retentar. Contate o suporte para validar habilitação da conta.
404Recurso não encontrado.Não retentar. Confirme id ou clientReference.
409Conflito (duplicado, recurso em estado inválido).Não retentar. Consulte via GET o estado atual antes de tentar novamente.
422Validação semântica falhou.Não retentar. Corrija o payload conforme message.
429Rate limit atingido.Aguarde, faça backoff exponencial e tente de novo.
5xxErro do servidor PayZu.Retry com backoff exponencial (1s → 2s → 4s → 8s, máx 4 tentativas).
TimeoutSem resposta dentro do prazo.A operação pode ter sido aplicada. Consulte por clientReference antes de recriar.

Helper de retry

Retry só em 429 e 5xx. Nunca em 4xx.

ATTEMPTS=4
DELAY=1

for i in $(seq 1 $ATTEMPTS); do
  STATUS=$(curl -s -o /tmp/resp.json -w "%{http_code}" \
    -X POST https://api.payzu.processamento.com/v1/pix \
    -H "Authorization: Bearer $TOKEN" \
    -H "Content-Type: application/json" \
    -d '{"amount":99.90,"clientReference":"order-1234"}')

  case $STATUS in
    2*) cat /tmp/resp.json; exit 0 ;;
    429|5*) sleep $DELAY; DELAY=$((DELAY*2)) ;;
    *) echo "Erro $STATUS"; cat /tmp/resp.json; exit 1 ;;
  esac
done
echo "Max retries excedido"
exit 1
async function withRetry<T>(
  fn: () => Promise<Response>,
  attempts = 4,
): Promise<T> {
  let lastErr: unknown;
  for (let i = 0; i < attempts; i++) {
    try {
      const res = await fn();
      if (res.ok) return res.json();

      if (res.status >= 400 && res.status < 500 && res.status !== 429) {
        const body = await res.text();
        throw new Error(`Client error ${res.status}: ${body}`);
      }
    } catch (err) {
      lastErr = err;
    }

    const delay = Math.min(8000, 1000 * 2 ** i) + Math.random() * 250;
    await new Promise((r) => setTimeout(r, delay));
  }
  throw lastErr ?? new Error('Max retries exceeded');
}
import time, random, requests

def with_retry(fn, attempts=4):
    last_err = None
    for i in range(attempts):
        try:
            res = fn()
            if res.ok:
                return res.json()
            if 400 <= res.status_code < 500 and res.status_code != 429:
                raise RuntimeError(f'Client error {res.status_code}: {res.text}')
        except Exception as e:
            last_err = e

        delay = min(8.0, 1.0 * (2 ** i)) + random.random() * 0.25
        time.sleep(delay)

    raise last_err or RuntimeError('Max retries exceeded')
func WithRetry(fn func() (*http.Response, error), attempts int) ([]byte, error) {
    var lastErr error
    for i := 0; i < attempts; i++ {
        res, err := fn()
        if err == nil {
            defer res.Body.Close()
            if res.StatusCode >= 200 && res.StatusCode < 300 {
                return io.ReadAll(res.Body)
            }
            if res.StatusCode >= 400 && res.StatusCode < 500 && res.StatusCode != 429 {
                body, _ := io.ReadAll(res.Body)
                return nil, fmt.Errorf("client error %d: %s", res.StatusCode, body)
            }
        }
        lastErr = err

        delay := time.Duration(math.Min(8000, 1000*math.Pow(2, float64(i)))) * time.Millisecond
        time.Sleep(delay + time.Duration(rand.Intn(250))*time.Millisecond)
    }
    return nil, lastErr
}

Timeout: a armadilha do "pode ter dado certo"

Timeout não é equivalente a falha. A PayZu pode ter recebido, processado e gravado a transação, e a resposta apenas não voltou. Sua app não sabe.

Solução: use clientReference único e consulte antes de retentar.

async function createOrRetry(orderId: string, amount: number) {
  const ref = `order-${orderId}`;
  try {
    return await withRetry(() => postPix({ amount, clientReference: ref }));
  } catch (err) {
    // pode ter dado certo apesar do erro/timeout
    const existing = await fetch(
      `https://api.payzu.processamento.com/v1/pix?clientReference=${ref}`,
      { headers },
    ).then((r) => (r.ok ? r.json() : null));
    if (existing) return existing;
    throw err;
  }
}

Observabilidade do erro

Sempre logue, no mínimo:

CampoPor quê
requestIdVem nas respostas de erro PayZu. Suporte rastreia direto.
id localSeu identificador (pedido, saque).
id PayZuSe já houver.
endToEndIdÚtil para rastrear no Bacen em disputa.
clientReferenceA chave de correlação universal.
HTTP status + messageA causa raiz quase sempre está em message.
Tentativa N de MDiferencia primeira tentativa de retry.
log.error('PayZu /pix falhou', {
  requestId: body.requestId,
  status: res.status,
  message: body.message,
  clientReference: ref,
  attempt: i + 1,
  attempts,
});

Mensagens de erro úteis para o usuário final

Não exponha message cru. Traduza para algo acionável:

Erro PayZuMensagem para o usuário
401 Unauthorized"Erro de configuração. Contate o suporte com o código requestId."
400 amount must be >= 1"Valor mínimo da cobrança é R$ 1,00."
400 invalid pixKey"Chave Pix inválida. Confira e tente novamente."
429 Too Many Requests"Estamos com muitas requisições. Tente em instantes."
5xx"Sistema temporariamente indisponível. Já estamos olhando."

Armadilhas comuns

ArmadilhaSintoma
Retentar em 400Spam contra a API, mesmo erro N vezes
Retentar em 401 sem rotacionar tokenToken vaza ainda mais no log
Sem backoff (retry imediato em loop)Vira rate limit, depois fica banido
Sem jitter no backoffN clientes batem ao mesmo tempo, "thundering herd"
Tratar timeout como falha definitivaCliente cobra 2x do usuário
Não logar requestIdSuporte não consegue investigar

Abrir suporte com o requestId

Tem o requestId salvo? Manda direto pro time.

On this page