# Pay a Pix copy-and-paste code (/en/docs/conta-digital/qr-payments)

<QuickLinks>
  <QuickLink href="/docs/conta-digital/endpoints/pix/post_pix_qr_payment" title="Pay Pix code" method="POST" path="/transactions/pix/qr-payments" />

  <QuickLink href="/docs/conta-digital/endpoints/pix/post_pix_decode" title="Decode Pix code" method="POST" path="/transactions/pix/decode" />

  <QuickLink href="/docs/conta-digital/endpoints/pix/post_pix_destination" title="Look up recipient" method="POST" path="/transactions/pix/destination" />

  <QuickLink href="/docs/conta-digital/withdrawals" title="Pix withdrawals" />
</QuickLinks>

A Pix copy-and-paste payment works like a [withdrawal](/docs/conta-digital/withdrawals): same response, same tracking and the same limits, with its own fee. The destination comes from the code, and so does the amount, when the code sets one.

<Mermaid
  chart="`
flowchart LR
  A[&#x22;Decode the Pix copy-and-paste code (optional)&#x22;] --> B[&#x22;Pay the Pix copy-and-paste code&#x22;]
  B --> C[&#x22;Amount and fee leave the balance&#x22;]
  C --> D[&#x22;WITHDRAW_COMPLETED webhook&#x22;]
  C -.->|&#x22;failed&#x22;| E[&#x22;WITHDRAW_FAILED webhook&#x22;]

  click A &#x22;/docs/conta-digital/endpoints/pix/post_pix_decode&#x22; &#x22;Decode Pix code&#x22;
  click B &#x22;/docs/conta-digital/endpoints/pix/post_pix_qr_payment&#x22; &#x22;Pay Pix code&#x22;
  click D &#x22;/docs/conta-digital/webhooks&#x22; &#x22;Webhooks&#x22;

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

<Steps>
  <Step>
    ### Decode the Pix copy-and-paste code [#decode-the-pix-copy-and-paste-code]

    Optional. [`POST /transactions/pix/decode`](/docs/conta-digital/endpoints/pix/post_pix_decode), scope `PIX_DICT_READ`, reads the code without paying and without querying the bank: it does not count toward the lookup limit and does not say who the holder is.

    <Tabs items="['curl', 'Node.js']">
      <Tab value="curl">
        ```bash
        curl -X POST https://api.hub.payzu.com.br/api/v1/transactions/pix/decode \
          -H "Authorization: Bearer $PAYZU_TOKEN" \
          -H "Content-Type: application/json" \
          -d '{ "brCode": "00020126400014br.gov.bcb.pix0118fulano@exemplo.com520400005303986540525.005802BR5913FULANO DE TAL6009SAO PAULO62140510PEDIDO482163048572" }'
        ```
      </Tab>

      <Tab value="Node.js">
        ```ts
        const res = await fetch('https://api.hub.payzu.com.br/api/v1/transactions/pix/decode', {
          method: 'POST',
          headers: {
            Authorization: `Bearer ${process.env.PAYZU_TOKEN}`,
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({
            brCode: '00020126400014br.gov.bcb.pix0118fulano@exemplo.com520400005303986540525.005802BR5913FULANO DE TAL6009SAO PAULO62140510PEDIDO482163048572',
          }),
        });
        const codigo = await res.json();
        ```
      </Tab>
    </Tabs>

    ```json
    {
      "pixKey": "fulano@exemplo.com",
      "url": null,
      "amount": 2500,
      "merchantName": "FULANO DE TAL",
      "merchantCity": "SAO PAULO",
      "txid": "PEDIDO4821",
      "isDynamic": false,
      "isAmountFixed": true
    }
    ```

    `isAmountFixed` says whether the code already carries the amount. `merchantName` and `merchantCity` are what the code's creator wrote, without verification; to know the holder, use [Look up recipient](/docs/conta-digital/recipient). In a dynamic code, `pixKey` comes `null` and only `url` is filled in; the holder and the amount come from the [recipient lookup](/docs/conta-digital/recipient).
  </Step>

  <Step>
    ### Pay the Pix copy-and-paste code [#pay-the-pix-copy-and-paste-code]

    [`POST /transactions/pix/qr-payments`](/docs/conta-digital/endpoints/pix/post_pix_qr_payment), scope `WITHDRAW`. Send the full code in `brCode`, as it was read: a key decoded by your system is not accepted in its place. Send `amount`, in cents, only when the code does not set an amount.

    Generate an `Idempotency-Key` for each payment and, if you repeat the call, send the same one.

    <Tabs items="['curl', 'Node.js']">
      <Tab value="curl">
        ```bash
        IDEMPOTENCY_KEY=$(uuidgen)

        curl -X POST https://api.hub.payzu.com.br/api/v1/transactions/pix/qr-payments \
          -H "Authorization: Bearer $PAYZU_TOKEN" \
          -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
          -H "Content-Type: application/json" \
          -d '{
            "brCode": "00020126400014br.gov.bcb.pix0118fulano@exemplo.com520400005303986540525.005802BR5913FULANO DE TAL6009SAO PAULO62140510PEDIDO482163048572",
            "comment": "Pedido 4821"
          }'
        ```
      </Tab>

      <Tab value="Node.js">
        ```ts
        import crypto from 'node:crypto';

        const idempotencyKey = crypto.randomUUID();

        const res = await fetch('https://api.hub.payzu.com.br/api/v1/transactions/pix/qr-payments', {
          method: 'POST',
          headers: {
            Authorization: `Bearer ${process.env.PAYZU_TOKEN}`,
            'Idempotency-Key': idempotencyKey,
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({
            brCode: '00020126400014br.gov.bcb.pix0118fulano@exemplo.com520400005303986540525.005802BR5913FULANO DE TAL6009SAO PAULO62140510PEDIDO482163048572',
            comment: 'Pedido 4821',
          }),
        });
        const pagamento = await res.json();
        ```
      </Tab>
    </Tabs>

    `comment` goes to the recipient; without it, the recipient name in the code goes instead. `callbackUrl` receives this payment's webhooks and requires the [callback secret](/docs/conta-digital/webhooks#operation-callbackurl). All fields are in [Pay Pix code](/docs/conta-digital/endpoints/pix/post_pix_qr_payment).
  </Step>

  <Step>
    ### Store the payment [#store-the-payment]

    The response (`201`) has the same format as a [withdrawal](/docs/conta-digital/withdrawals#withdrawal-status). Store the `id`.

    ```json
    {
      "id": "hubp-20261005H2P6XC8VNM127431",
      "status": "APPROVED",
      "amount": 2500,
      "serviceFee": 100,
      "totalDebited": 2600,
      "pixKey": "f***@exemplo.com",
      "comment": "Pedido 4821",
      "e2e": null,
      "providerRejectedReason": null,
      "callbackUrl": null,
      "createdAt": "2026-10-05T14:40:11.002Z",
      "sentAt": "2026-10-05T14:40:11.380Z",
      "approvedAt": "2026-10-05T14:40:11.702Z",
      "confirmedAt": null
    }
    ```

    `pixKey` comes masked. `APPROVED` means the payment is on its way, not that the money was delivered.
  </Step>

  <Step>
    ### Confirm delivery [#confirm-delivery]

    The payment is delivered when the `WITHDRAW_COMPLETED` webhook arrives, with `status: "CONFIRMED"` and `operation: "EXTERNAL_PAYMENT"`. If it fails, `WITHDRAW_FAILED` arrives, and the amount and the fee come back to the balance.

    To look it up, use the withdrawal route: [`GET /transactions/withdraw/{withdrawId}`](/docs/conta-digital/endpoints/withdrawals/get_withdraw).
  </Step>
</Steps>

## Amount [#amount]

| The code         | `amount` sent   | Result                                                                       |
| ---------------- | --------------- | ---------------------------------------------------------------------------- |
| Sets an amount   | None            | Pays the code's amount.                                                      |
| Sets an amount   | The same        | Pays.                                                                        |
| Sets an amount   | A different one | `422` `QR_AMOUNT_MISMATCH`, with `details.expected` and `details.requested`. |
| Does not set one | An amount       | Pays the amount sent.                                                        |
| Does not set one | None            | `422` `QR_AMOUNT_REQUIRED`.                                                  |

## Dynamic code [#dynamic-code]

A dynamic code carries only a link, which PayZu resolves at the bank before paying; nothing leaves the balance before that. The body and the response are the same.

* If resolving fails, the rejection uses the [recipient lookup](/docs/conta-digital/recipient) codes: `404` `PIX_DEST_PIX_KEY`, `503` `PIX_DEST_UNAVAILABLE` or `PIX_DEST_THROTTLED`, `502` `PIX_DEST_NOT_AUTHORIZED_AT_PROVIDER`.
* If the amount printed in the code differs from the amount the bank returns for it, the payment is refused with `422` `QR_AMOUNT_DISAGREES`.

## Repeated request [#repeated-request]

The `Idempotency-Key` follows the [withdrawal](/docs/conta-digital/withdrawals#repeated-request) rules, and the comparison also considers the code being paid. For a dynamic code, the code is resolved before the repetition is checked, so repeating can bring the resolution rejections instead of the original payment.

On a `502`, the payment may have gone out. Repeat with the same `Idempotency-Key` or look for the payment in [`GET /transactions/withdraw`](/docs/conta-digital/endpoints/withdrawals/get_withdraws) before paying again.

## Fee and limits [#fee-and-limits]

* The fee is the Pix copy-and-paste payment fee, `externalPayment` in the [limits](/docs/conta-digital/statement#limits).
* The minimum and the maximum are the withdrawal ones. So are the daily cap and the request limit, counted together with withdrawals.
* A code that was already paid or has expired is refused by the bank.
* A corrupted code (`QR_CRC`), a code out of format (`QR_MALFORMED`) or a code that is not Pix (`QR_NOT_PIX`) is refused with `400`, when decoding and when paying.

The rejections, with the `code`, are in [Pay Pix code](/docs/conta-digital/endpoints/pix/post_pix_qr_payment).