# Authentication (/en/docs/pix-processamento/authentication)

<QuickLinks>
  <QuickLink href="/docs/pix-processamento/endpoints" title="API Reference" />

  <QuickLink href="/docs/pix-processamento/best-practices/security" title="Security" />

  <QuickLink href="/docs/pix-processamento/glossary" title="Glossary" />
</QuickLinks>

<Mermaid
  chart="`
flowchart LR
  A[&#x22;Your application&#x22;] -->|&#x22;Authorization: Bearer YOUR_TOKEN&#x22;| B[&#x22;PayZu API&#x22;]
  B --> C{&#x22;Validation&#x22;}
  C -->|Valid token| OK[&#x22;200 OK&#x22;]
  C -->|Missing/invalid token| E1[&#x22;401 Unauthorized&#x22;]
  C -->|No permission| E2[&#x22;403 Forbidden&#x22;]

  click OK &#x22;/en/docs/pix-processamento/endpoints&#x22; &#x22;Endpoint list&#x22;
  click E1 &#x22;#401-unauthorized&#x22; &#x22;Resolve 401&#x22;
  click E2 &#x22;#403-forbidden&#x22; &#x22;Resolve 403&#x22;
  click A &#x22;/en/docs/pix-processamento/best-practices/security&#x22; &#x22;Where to store the token&#x22;

  style A fill:#f59e0b,stroke:#d97706,color:#ffffff
  style OK fill:#14ce71,stroke:#0eb464,color:#ffffff
  style E1 fill:#ef4444,stroke:#dc2626,color:#ffffff
  style E2 fill:#ef4444,stroke:#dc2626,color:#ffffff
`"
/>

## How to send [#how-to-send]

Every call requires **two mandatory headers**:

```http
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json
```

Example of an authenticated call to query the balance:

<Tabs items="['curl', 'Node.js', 'Python', 'Go', 'PHP']">
  <Tab value="curl">
    ```bash
    curl https://api.payzu.processamento.com/v1/user/balance \
      -H "Authorization: Bearer $PAYZU_TOKEN" \
      -H "Content-Type: application/json"
    ```
  </Tab>

  <Tab value="Node.js">
    ```ts
    const res = await fetch('https://api.payzu.processamento.com/v1/user/balance', {
      headers: {
        Authorization: `Bearer ${process.env.PAYZU_TOKEN}`,
        'Content-Type': 'application/json',
      },
    });
    const balance = await res.json();
    ```
  </Tab>

  <Tab value="Python">
    ```python
    import os
    import requests

    res = requests.get(
        'https://api.payzu.processamento.com/v1/user/balance',
        headers={
            'Authorization': f'Bearer {os.environ["PAYZU_TOKEN"]}',
            'Content-Type': 'application/json',
        },
    )
    balance = res.json()
    ```
  </Tab>

  <Tab value="Go">
    ```go
    req, _ := http.NewRequest("GET", "https://api.payzu.processamento.com/v1/user/balance", nil)
    req.Header.Set("Authorization", "Bearer " + os.Getenv("PAYZU_TOKEN"))
    req.Header.Set("Content-Type", "application/json")
    res, err := http.DefaultClient.Do(req)
    ```
  </Tab>

  <Tab value="PHP">
    ```php
    <?php
    $ch = curl_init('https://api.payzu.processamento.com/v1/user/balance');
    curl_setopt_array($ch, [
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PAYZU_TOKEN'),
        'Content-Type: application/json',
      ],
    ]);
    $balance = json_decode(curl_exec($ch), true);
    ```
  </Tab>
</Tabs>

## Where to store [#where-to-store]

<Callout type="warn">
  Never expose the token on the front-end, in a public repository, or in logs.
  Treat it as a password: store it in a vault and inject it via environment variable.
</Callout>

Recommendations:

* **Google Secret Manager**, ideal if you already use GCP.
* **HashiCorp Vault**, for self-hosted setups.
* **AWS Secrets Manager**, the AWS equivalent.
* **Environment variable in CI**, never commit it.

## Error format [#error-format]

**Every error response from PayZu** (4xx and 5xx) follows the same format. The most important field is `requestId`, which uniquely identifies the call in PayZu's internal logs.

```json
{
  "errorCode": "PZA203",
  "message": "Access not allowed from this IP address.",
  "statusCode": 403,
  "requestId": "cmp70zh4008dx01s6bwjb5bez"
}
```

| Field        | Purpose                                                                                                 |
| ------------ | ------------------------------------------------------------------------------------------------------- |
| `errorCode`  | Stable code from the catalog (e.g. `PZA203`). Program your logic against it, not the message.           |
| `message`    | Description in PT of what happened. Use in logs, not for end users.                                     |
| `statusCode` | HTTP response code (mirrors the status).                                                                |
| `requestId`  | **Unique ID of the call at PayZu**. Send this ID when opening a support ticket, they trace it directly. |

The full catalog, including the optional `details[]` and `retryAfterSeconds` fields, is in [Error codes](/docs/pix-processamento/error-codes).

**Always log the `requestId` in your errors**: it is the first thing support asks for, and the logging snippet with the full retry strategy is in [Error handling](/docs/pix-processamento/best-practices/errors).

### Opening support with the requestId [#opening-support-with-the-requestid]

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

## Token scopes [#token-scopes]

A token carries one or more scopes, and they define which routes it opens. The same token can have both `DEPOSIT` and `WITHDRAW`.

| Scope      | What it opens                                                                                                                                                                                        |
| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `DEPOSIT`  | Incoming Pix charges: `POST /v1/pix`, `GET /v1/pix` and `GET /v1/pix/qr-code/:transactionId`.                                                                                                        |
| `WITHDRAW` | Payments and outbound movement: `POST /v1/withdraw`, `POST /v1/withdraw/qrcode`, `GET /v1/withdraw`, `POST /v1/internal-transfer`, `GET /v1/internal-transfer` and `POST /v1/refund/:transactionId`. |

DICT lookups (`GET /v1/pix/key` and `POST /v1/pix/qrcode/read`) accept either scope.

A token without the scope the route requires receives `403` with `errorCode` `PZA200`.

## Resolving errors [#resolving-errors]

### 401 Unauthorized [#401-unauthorized]

The most common causes, in order:

1. **Missing token**, the `Authorization` header was not sent.
2. **Incorrect token**, typo, extra whitespace, wrong encoding.
3. **Revoked token**, it was rotated and you are using the old one.

Example response:

```json
{
  "errorCode": "PZA100",
  "message": "Authentication required or invalid token.",
  "statusCode": 401,
  "requestId": "cmou00000abcdef01s6ghij1k2lm"
}
```

### 403 Forbidden [#403-forbidden]

The token is valid but does not have permission for the operation. Check whether the
endpoint requires an additional scope or whether your account is enabled for the
resource (for example, internal transfer may require prior approval).

## Rotation [#rotation]

If the token leaks, contact PayZu support immediately to issue
a new token and revoke the previous one.

## IP whitelist for Pix payments and transfers [#ip-whitelist-for-pix-payments-and-transfers]

An extra layer of protection for the operations that move money out of the account. When the whitelist is active, `POST /v1/withdraw`, `POST /v1/withdraw/qrcode` and `POST /v1/internal-transfer` only accept calls from the registered IPs. Any other IP receives `403` with `errorCode` `PZA203`, even with a valid token. The `GET` queries of these same routes go through the same validation.

### How to manage [#how-to-manage]

Management is self-service in the web panel, under the **Security** menu, **IP whitelist** section. Every addition or removal requires step-up confirmation (operation password), and all changes are recorded for audit.

Limits:

* Up to **20 active IPs** per account.
* Up to **5 additions every 5 minutes**.

<Callout type="warn">
  An account locked for changes responds `403` with `errorCode` `PZA204` when trying
  to add or remove IPs. In that case, contact support.
</Callout>

### Best practices [#best-practices]

* Register the **fixed egress IPs** of your infrastructure (NAT/egress). A dynamic IP from a local machine will break on the first rotation.
* When migrating infrastructure, **add the new IP before removing the old one**. That way Pix payments keep flowing during the transition.
* Treat `PZA203` in your code as a configuration error, not a business error: alert your infra team instead of retrying.