# Integration security (/en/docs/pix-processamento/best-practices/security)

<QuickLinks>
  <QuickLink href="/docs/pix-processamento/authentication" title="Authentication" />

  <QuickLink href="/docs/pix-processamento/best-practices/dict" title="DICT Lookup" />

  <QuickLink href="/docs/pix-processamento/two-factor" title="2FA" />
</QuickLinks>

The Pix integration handles real money. Every layer must have its own line of defense.

## Token storage [#token-storage]

Bearer token is PayZu's **only credential**. Whoever has it can move the balance.

| Where to store                    | OK?   |
| --------------------------------- | ----- |
| **Google Secret Manager**         | Yes   |
| **AWS Secrets Manager**           | Yes   |
| **HashiCorp Vault**               | Yes   |
| **Environment variable in CI**    | Yes   |
| `.env` in production              | No    |
| Hardcoded in code                 | No    |
| `localStorage` / `sessionStorage` | No    |
| Front-end (Web, Mobile)           | Never |

<Callout type="warn">
  **Never expose the token on the front-end.** Every PayZu call must go through your backend, which injects the `Authorization` on the server.
</Callout>

## Log masking [#log-masking]

Configure your logger to mask the `Authorization` header and sensitive payload fields (`payerDocument`, `pixKey`, etc).

<Tabs items="['Node.js (pino)', 'Python (logging)', 'Go (zap)']">
  <Tab value="Node.js (pino)">
    ```ts
    import pino from 'pino';

    const logger = pino({
      redact: {
        paths: [
          'req.headers.authorization',
          'res.headers.authorization',
          '*.payerDocument',
          '*.pixKey',
        ],
        censor: '[REDACTED]',
      },
    });
    ```
  </Tab>

  <Tab value="Python (logging)">
    ```python
    import logging, re

    class RedactFilter(logging.Filter):
        def filter(self, record):
            if isinstance(record.msg, str):
                record.msg = re.sub(r'Bearer\s+[A-Za-z0-9._\-]+', 'Bearer [REDACTED]', record.msg)
            return True

    logging.getLogger().addFilter(RedactFilter())
    ```
  </Tab>

  <Tab value="Go (zap)">
    ```go
    logger, _ := zap.NewProduction()
    defer logger.Sync()

    logger.Info("payzu call",
        zap.String("url", url),
        zap.String("authorization", "[REDACTED]"),
    )
    ```
  </Tab>
</Tabs>

## Token rotation [#token-rotation]

| When to rotate                                 | Action                               |
| ---------------------------------------------- | ------------------------------------ |
| Someone with access leaves the company         | Immediate                            |
| Suspected leak (accidental commit, public log) | Immediate + audit                    |
| Preventive rotation                            | Quarterly or semi-annually           |
| After pen-test                                 | Immediate if exposed during the test |

Request the rotation from PayZu support. Have a **planned rollover** (two active tokens for a window) so you don't bring production down.

## Webhook endpoint protection [#webhook-endpoint-protection]

Only accept callbacks from PayZu's official IP. Request the current IP from support.

<Tabs items="['Nginx', 'Cloudflare', 'Express']">
  <Tab value="Nginx">
    ```nginx
    location /webhooks/payzu {
      allow 35.199.0.0/16;
      deny all;
      proxy_pass http://backend;
    }
    ```
  </Tab>

  <Tab value="Cloudflare">
    Create a WAF rule: `(http.request.uri.path eq "/webhooks/payzu" and ip.src ne <IP_PAYZU>)` → Block.
  </Tab>

  <Tab value="Express">
    ```ts
    const PAYZU_IPS = (process.env.PAYZU_WEBHOOK_IPS ?? '').split(',');

    app.post('/webhooks/payzu', (req, res, next) => {
      const ip = req.ip;
      if (!PAYZU_IPS.includes(ip)) return res.status(403).end();
      next();
    });
    ```
  </Tab>
</Tabs>

## DICT validation before paying [#dict-validation-before-paying]

On Pix payments by key, validate the holder via DICT before transferring. It detects bank/CPF changes and prevents paying the wrong recipient.

```ts
async function safeWithdraw(pixKey: string, pixType: string, expectedName: string, amount: number) {
  const url = new URL('https://api.payzu.processamento.com/v1/pix/key');
  url.searchParams.set('pixKey', pixKey);

  const dict = await fetch(url, {
    headers: {
      Authorization: `Bearer ${process.env.PAYZU_TOKEN}`,
      'Content-Type': 'application/json',
    },
  }).then(r => r.json());

  if (dict.name?.toLowerCase().trim() !== expectedName.toLowerCase().trim()) {
    throw new Error(`Holder mismatch: expected ${expectedName}, found ${dict.name}`);
  }

  return createWithdraw({ pixKey, pixType, amount });
}
```

Details at [DICT Lookup](/docs/pix-processamento/best-practices/dict).

## 2FA on sensitive operations [#2fa-on-sensitive-operations]

Consider additional 2FA **in your application** (not at PayZu) before:

* Pix payment above a high limit.
* Creating/changing a payment Pix key.
* Admin login with permission to move balance.

PayZu already validates on the backend, but application-level 2FA reduces the blast radius of a compromised session. Details at [2FA](/docs/pix-processamento/two-factor).

## Principle of least privilege [#principle-of-least-privilege]

| Setup                              | Recommendation                                   |
| ---------------------------------- | ------------------------------------------------ |
| Single token for prod + dev        | Separate: sandbox token vs prod token.           |
| Same token shared across services  | One token per service/team (to audit usage).     |
| Devs with access to the prod token | Only infra/SRE should have it; devs use sandbox. |

## Common pitfalls [#common-pitfalls]

| Pitfall                                   | Symptom                                        |
| ----------------------------------------- | ---------------------------------------------- |
| Token in a versioned `.env`               | Leaks on the first wrong `git push --force`    |
| Logger prints the `Authorization` header  | Token leaks in any log capture                 |
| Webhook open to any IP                    | Forged payloads may be accepted                |
| Paying without DICT on Pix payment by key | May pay the wrong recipient                    |
| Same token in sandbox and production      | An environment mistake becomes a prod incident |