# Tratamento de erros (/docs/pix-processamento/best-practices/errors)



<QuickLinks>
  <QuickLink href="/docs/pix-processamento/error-codes" title="Códigos de erro" />

  <QuickLink href="/docs/pix-processamento/authentication" title="Autenticação" />

  <QuickLink href="/docs/pix-processamento/best-practices/idempotency" title="Idempotência" />
</QuickLinks>

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

<Mermaid
  chart="`
flowchart TD
  A[&#x22;Chamada PayZu&#x22;]
  A --> B{&#x22;Status code&#x22;}
  B -->|&#x22;2xx&#x22;| OK[&#x22;Sucesso&#x22;]
  B -->|&#x22;4xx (exceto 424/429)&#x22;| C[&#x22;Erro do seu payload<br/>NÃO retentar&#x22;]
  B -->|&#x22;429&#x22;| D[&#x22;Rate limit<br/>backoff exponencial&#x22;]
  B -->|&#x22;424&#x22;| G[&#x22;Falha da instituição<br/>financeira<br/>retry com backoff&#x22;]
  B -->|&#x22;5xx&#x22;| E[&#x22;Erro PayZu<br/>retry com backoff&#x22;]
  B -->|&#x22;Timeout&#x22;| F[&#x22;Operação pode<br/>ter sido aplicada&#x22;]

  click C &#x22;/docs/pix-processamento/error-codes&#x22; &#x22;Códigos de erro&#x22;
  click D &#x22;#helper-de-retry&#x22; &#x22;Helper de retry&#x22;
  click G &#x22;#helper-de-retry&#x22; &#x22;Helper de retry&#x22;
  click E &#x22;#helper-de-retry&#x22; &#x22;Helper de retry&#x22;
  click F &#x22;#timeout-a-armadilha-do-pode-ter-dado-certo&#x22; &#x22;Armadilha do timeout&#x22;

  style OK fill:#14ce71,stroke:#0eb464,color:#ffffff
  style C fill:#ef4444,stroke:#dc2626,color:#ffffff
  style D fill:#f59e0b,stroke:#d97706,color:#ffffff
  style G fill:#f59e0b,stroke:#d97706,color:#ffffff
  style E fill:#f59e0b,stroke:#d97706,color:#ffffff
  style F fill:#f59e0b,stroke:#d97706,color:#ffffff
`"
/>

A tabela completa de códigos HTTP e o catálogo de `errorCode`, com o que fazer em cada um, estão em [Códigos de erro](/docs/pix-processamento/error-codes); esta página cobre a estratégia: quando retentar, como fazer backoff e o que logar.

## Helper de retry [#helper-de-retry]

Retry em `429`, `5xx` e `424`. Nos demais `4xx`, **nunca**.

<Tabs items="['curl', 'Node.js']">
  <Tab value="curl">
    ```bash
    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 ;;
        424|429|5*) sleep $DELAY; DELAY=$((DELAY*2)) ;;
        *) echo "Erro $STATUS"; cat /tmp/resp.json; exit 1 ;;
      esac
    done
    echo "Max retries excedido"
    exit 1
    ```
  </Tab>

  <Tab value="Node.js">
    ```ts
    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 && res.status !== 424) {
            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');
    }
    ```
  </Tab>
</Tabs>

## Timeout: a armadilha do "pode ter dado certo" [#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.

```ts
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 [#observabilidade-do-erro]

Sempre logue, no mínimo:

| Campo                 | Por quê                                                   |
| --------------------- | --------------------------------------------------------- |
| `requestId`           | Vem nas respostas de erro PayZu. Suporte rastreia direto. |
| `id` local            | Seu identificador (pedido, saque).                        |
| `id` PayZu            | Se já houver.                                             |
| `endToEndId`          | Útil para rastrear no Bacen em disputa.                   |
| `clientReference`     | A chave de correlação universal.                          |
| HTTP status + message | A causa raiz quase sempre está em `message`.              |
| Tentativa N de M      | Diferencia primeira tentativa de retry.                   |

```ts
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 [#mensagens-de-erro-úteis-para-o-usuário-final]

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

| Erro PayZu                     | Mensagem 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."                    |
| `424` (instituição financeira) | "Instituição financeira instável no momento. Tente em instantes."   |
| `429 Too Many Requests`        | "Estamos com muitas requisições. Tente em instantes."               |
| `5xx`                          | "Sistema temporariamente indisponível. Já estamos olhando."         |

## Armadilhas comuns [#armadilhas-comuns]

| Armadilha                              | Sintoma                                            |
| -------------------------------------- | -------------------------------------------------- |
| Retentar em `400`                      | Spam contra a API, mesmo erro N vezes              |
| Retentar em `401` sem rotacionar token | Token vaza ainda mais no log                       |
| Sem backoff (retry imediato em loop)   | Vira rate limit, depois fica banido                |
| Sem jitter no backoff                  | N clientes batem ao mesmo tempo, "thundering herd" |
| Tratar timeout como falha definitiva   | Cliente cobra 2x do usuário                        |
| Não logar `requestId`                  | Suporte não consegue investigar                    |

## Abrir suporte com o `requestId` [#abrir-suporte-com-o-requestid]

Tem o `requestId` salvo? Manda direto pro time.

<QuickLinks>
  <QuickLink href="https://suporte.payzu.com.br/portal/pt-br/newticket?departmentId=1103699000000006907&layoutId=1103699000000074011" title="Abrir ticket" />

  <QuickLink href="https://suporte.payzu.com.br/portal/pt-br/kb/payzu" title="Base de conhecimento" />

  <QuickLink href="https://suporte.payzu.com.br" title="Portal de suporte" />
</QuickLinks>
