PayZuDocs

Getting started

From the credential created in the dashboard to the first paid charge, confirmed by webhook on your server.

The calls in this guide go to production: the charge created here is real.

Create the credential

In the Digital Account dashboard, at hub.payzu.com.br, open the credentials area and create one with the scopes your integration uses. Creating it asks for the account holder's operation PIN.

For this guide, select PAYMENT_WRITE, PAYMENT_READ and WEBHOOK_WRITE. What each scope allows is in Scopes.

The screen shows the client_id, the client_secret and the credential token (pzu_…).

The client_secret appears only once. Store it before leaving the screen.

Exchange the credential for a token

Send the client_id and the client_secret to POST /oauth/token. The returned token is valid for 15 minutes.

curl -X POST https://api.hub.payzu.com.br/api/v1/oauth/token \
  -u "$PAYZU_CLIENT_ID:$PAYZU_CLIENT_SECRET" \
  -d 'grant_type=client_credentials'
const credentials = Buffer.from(`${process.env.PAYZU_CLIENT_ID}:${process.env.PAYZU_CLIENT_SECRET}`).toString('base64');

const response = await fetch('https://api.hub.payzu.com.br/api/v1/oauth/token', {
  method: 'POST',
  headers: {
    Authorization: `Basic ${credentials}`,
    'Content-Type': 'application/x-www-form-urlencoded',
  },
  body: 'grant_type=client_credentials',
});

const { access_token, expires_in } = await response.json();
{
  "access_token": "eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIn0..mQ3Zy1hbVhpbXBsZQ.ZXhlbXBsbw.c2lnbmF0dXJl",
  "token_type": "Bearer",
  "expires_in": 900,
  "scope": "PAYMENT_WRITE PAYMENT_READ WEBHOOK_WRITE"
}

Send the access_token in Authorization: Bearer on the other calls. In the examples below, it is in $PAYZU_TOKEN. When the token expires, the API responds 401 with TOKEN_INVALID: request another one from the same route.

Register the webhook

Register the URL of your server that will receive the webhooks, with POST /transactions/webhooks (scope WEBHOOK_WRITE). The URL must be HTTPS and public.

curl -X POST https://api.hub.payzu.com.br/api/v1/transactions/webhooks \
  -H "Authorization: Bearer $PAYZU_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://sualoja.com.br/webhooks/payzu",
    "events": ["PAYMENT_PAID", "PAYMENT_EXPIRED"]
  }'
const res = await fetch('https://api.hub.payzu.com.br/api/v1/transactions/webhooks', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.PAYZU_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    url: 'https://sualoja.com.br/webhooks/payzu',
    events: ['PAYMENT_PAID', 'PAYMENT_EXPIRED'],
  }),
});
const { id, secret } = await res.json();

The 201 response carries the secret that signs each webhook. It appears only in this response: store it. You can also register the webhook in the dashboard.

If you get 403 with TOKEN_MISSING_SCOPE, the credential is missing a scope; its name comes in details.scope.

Create the charge

Create a charge with POST /transactions/payment (scope PAYMENT_WRITE). The amount goes in cents: 1500 is R$ 15.00.

curl -X POST https://api.hub.payzu.com.br/api/v1/transactions/payment \
  -H "Authorization: Bearer $PAYZU_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 1500,
    "method": "PIX",
    "description": "Pedido 4821",
    "externalRef": "pedido-4821",
    "metadata": { "pedido": "4821", "canal": "checkout-web" },
    "customer": { "name": "Maria Souza", "document": "52998224725" }
  }'
const res = await fetch('https://api.hub.payzu.com.br/api/v1/transactions/payment', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.PAYZU_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    amount: 1500,
    method: 'PIX',
    description: 'Pedido 4821',
    externalRef: 'pedido-4821',
    metadata: { pedido: '4821', canal: 'checkout-web' },
    customer: { name: 'Maria Souza', document: '52998224725' },
  }),
});
const cobranca = await res.json();

The 201 response carries the charge with status: "PENDING" and the Pix copy-and-paste code in pix.qrCodeText. Show it to the customer and generate the QR code from it. Repeating the call with the same externalRef returns the same charge (200) without creating another one.

Receive the PAYMENT_PAID

When the customer pays, your server receives the PAYMENT_PAID:

POST /webhooks/payzu
Content-Type: application/json
X-Payzu-Event: PAYMENT_PAID
X-Payzu-Delivery: cmu1r7x2k000a01s6h4f2b9qd
X-Payzu-Timestamp: 1791210790441
X-Payzu-Signature: sha256=8f3b2c1d...

{
  "event": "PAYMENT_PAID",
  "id": "cmu1r7x2k000a01s6h4f2b9qd",
  "sentAt": "2026-10-05T14:33:10.441Z",
  "accountId": "cmu0z8k2a000001s6acct0001",
  "data": {
    "paymentId": "cmu2wbljx0000e8gtlic8q1gi",
    "status": "PAID",
    "amount": 1500,
    "serviceFee": 105,
    "netAmount": 1395,
    "metadata": { "pedido": "4821", "canal": "checkout-web" },
    "externalRef": "pedido-4821",
    "endToEndId": "E99999999202610051433a1b2c3d4e5f"
  }
}

Check the signature with the secret from the webhook registration, as in Webhooks, and respond with any 2xx within 10 seconds. Release the order here, on PAYMENT_PAID: the externalRef and the metadata from creation come back in data.

Check without waiting for the webhook

Get the charge with GET /transactions/payment/{paymentId} (scope PAYMENT_READ), using the id from the creation response or the paymentId from the webhook:

curl https://api.hub.payzu.com.br/api/v1/transactions/payment/hubp-20261005K7Q2M9XB4T127431 \
  -H "Authorization: Bearer $PAYZU_TOKEN"
const res = await fetch('https://api.hub.payzu.com.br/api/v1/transactions/payment/hubp-20261005K7Q2M9XB4T127431', {
  headers: {
    Authorization: `Bearer ${process.env.PAYZU_TOKEN}`,
  },
});
const cobranca = await res.json();

To find the charge for an order, use List charges with ?externalRef=.

Next steps

On this page