Accept payments from anywhere

Credit cards from customers in any country and Pix from Brazil, in one secure checkout. Withdraw by Pix or in USDT, wherever you are.

Withdraw in USDTNo monthly fee

Built to convert more sales

Our checkout runs on modern payment technology designed to raise your conversion rate. When a card is declined, the sale doesn't end: your customer can retry with another card or switch to Pix in one click, without leaving the page.

Payments coming in
  • Paid · cardLisbon, PTR$ 789,70
  • Paid · PixSão Paulo, BRR$ 92,15
  • Declined → PixRecife, BRsaved
  • Paid · cardAustin, USR$ 1.249,00

Sell in reais. Withdraw in USDT.

Your customers pay by card or Pix. You choose how to take the money out: by Pix in Brazil, or in USDT straight to your crypto wallet, from any country.

  • No Brazilian bank account neededWithdraw to a wallet you control, wherever you live.
  • Three networksTron (TRC-20), Ethereum (ERC-20) or Polygon.
  • Every withdrawal on recordSee the amount, the wallet and the transaction hash in your dashboard.

Secure from the first click

Your customers pay with confidence, and you never have to handle their card data.

Card data never touches your site

Customers enter their card on our checkout over an encrypted connection. Your servers never see or store card numbers.

Blocks card testing

Repeated failed card attempts on a checkout are stopped automatically, so fraudsters can't use your store to test stolen cards.

Confirmations you can trust

Every payment notice sent to your system is signed. No one can fake a paid order, and missed notices are resent for 3 days.

Built to sell worldwide

One checkout for cards and Pix. Send customers from your site or share the link by chat or email.

Global cards

Accept credit cards from customers in any country, charged in a single payment. They pay in reais and their bank converts the amount.

Pix

Brazil's instant payment. The customer scans a QR code in their bank app and pays in seconds.

USDT withdrawals

Take your balance out in USDT to your own wallet, from anywhere in the world.

High conversion

A modern payment infrastructure built to recover declined payments and turn more checkouts into revenue, automatically.

Simple to integrate

One API call creates the checkout. You get notified when it's paid. Read the docs

Create a chargePOST /v1/charges
curl https://api.payzoi.com/v1/charges \
  -H "Authorization: Bearer sk_test_•••" \
  -H "Content-Type: application/json" \
  -d '{
    "value": 5000,
    "description": "Order #123",
    "return_url": "https://yourstore.com/thanks",
    "primary_color": "#2446F5"
  }'

# → { "checkout_url": "https://pay.payzoi.com/c/ch_test_a1B2…" }

Pay only when you get paid

No monthly fee, no setup fee. A fee on each approved payment, and nothing else.

Pix

9%

per approved Pix

  • Paid straight from the customer's bank app
  • Charged at the full price, no discounts to manage
  • Nothing to pay if the code expires

Cards

9%

per approved card payment

  • Cards from customers in any country
  • High conversion rate
  • Declines cost nothing

Withdraw by Pix or in USDT. High volume? Talk to us about custom rates.

Start selling in three steps

Create your account

Sign up and tell us about your business. Your test key works right away, so you can build and test your integration.

Go live

Our team reviews your business details. Once approved, your live key is ready to take real payments.

Get paid

Sell through your checkout, then withdraw by Pix or in USDT to your wallet.

Ready to get paid from anywhere?

Create account
Carregando pagamento…

Pagar com cartão

Confira o número do cartão.
Use MM / AA, uma data futura.
3 ou 4 dígitos.
Digite o nome como está no cartão.
Pagamento criptografado. Seu banco pode pedir uma confirmação.

Pagamento aprovado

Seu pagamento foi confirmado.

Voltar para a loja

Accept a payment

Create a charge, send your customer to the checkout link, and we tell your server when it's paid. The customer pays on the PayZoi checkout by Pix, or by card when cards are enabled for your account, so payment data never touches your server.

  1. Create a charge with the value in cents. We return a checkout_url.
  2. Send your customer to the checkout_url. They choose how to pay among the methods available and pay.
  3. Get the confirmation: we send a charge.paid webhook to your server. If you set a return_url, we also send the customer back to your site.

Values are integers in cents: 5000 is R$ 50,00. All charges are in Brazilian reais (brl), for card and Pix alike. Dates are in UTC, in ISO 8601 format. Customers with cards issued in other countries also pay in reais; their bank converts the amount to their currency.

Release the order only when you receive charge.paid, or after checking the charge with Get a charge. The customer can close the browser before returning to your site.

To see every step working together, go to Complete example: a small store in one file that you can run with your test key.

Authentication

Send your secret key in every request:

Authorization: Bearer sk_test_your_secret_key
Content-Type: application/json
Accept: application/json

Base URL:

https://api.payzoi.com/v1

Get your keys in Dashboard → Integration. You only need the secret key: there is no public key in this integration. The key decides the mode. Requests and responses have the same format in both modes; in live mode, the payment methods available depend on your account.

sk_test_…test keyTest mode: card and Pix work end to end, with webhooks and a test balance, and no real money moves. See Testing.
sk_live_…live keyReal payments, after your account is activated for live mode.
Keep your secret key on your server. Never put it in a website, mobile app or public repository.

Create a charge

POST/v1/charges
valuerequiredintegerValue in cents. Minimum 50 (R$ 0,50). Live Pix charges need at least 500 (R$ 5,00).
descriptionstringWhat the customer is paying for, shown on the checkout. Up to 255 characters.
payment_methodsarray"credit_card", "pix" or both. Default both; the customer chooses one. With a test key, card and Pix are always available. With a live key, only the methods enabled for your account are offered. Cards are charged in a single payment (no installments).
expires_inintegerSeconds until the charge expires, 300 to 172800. Default 3600 (1 hour).
return_urlstringWhere we send the customer after paying, or when they leave the checkout. Without it, a customer who paid stays on our confirmation page, and “Back to your store” returns to the page they came from (it's hidden when there's no such page).
webhook_urlstringPublic https URL for this charge's webhooks. Default: the URL in your dashboard.
external_idstringYour order ID, up to 100 characters. Returned in webhooks and on the return URL.
customerobjectOptional name, email and document (CPF or CNPJ). Name and document fill in the checkout: Pix needs a name and a CPF or CNPJ, and if you don't send them, the customer types them. The checkout doesn't ask for an email: send it here to keep it with the payment (it comes back in payer.email).
metadataobjectUp to 20 pairs for your own use, like UTM parameters: keys up to 40 characters, string values up to 500. Returned in webhooks.
logo_urlstringYour logo on the checkout, https only. Square works best.
primary_colorstringYour brand color for the checkout buttons, as a 6-digit hex color like #0A7C66.
Set value on your server, from the order total in your database. Never take it from your page or form: the customer can change anything the browser sends.

Avoid duplicate charges

Send an Idempotency-Key header with a unique value per order, like your order ID. If a request times out and you retry with the same key within 24 hours, you get the charge created the first time (status 200 and the header Idempotent-Replayed: true) instead of a second charge.

Idempotency-Key: order-123
curl https://api.payzoi.com/v1/charges \
  -H "Authorization: Bearer sk_test_your_secret_key" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-123" \
  -d '{
    "value": 5000,
    "description": "Order #123",
    "payment_methods": ["credit_card", "pix"],
    "external_id": "123",
    "return_url": "https://yourstore.com/thanks",
    "webhook_url": "https://yourstore.com/webhooks/payzoi",
    "metadata": { "utm_source": "google" }
  }'

Response 201

{
  "id": "ch_test_Svy9MIb066RlzlC",
  "status": "pending",
  "value": 5000,
  "paid_value": null,
  "fee": null,
  "net_value": null,
  "description": "Order #123",
  "external_id": "123",
  "payment_methods": ["credit_card", "pix"],
  "logo_url": null,
  "primary_color": null,
  "payment_method": null,
  "payment": null,
  "card": null,
  "last_decline_reason": null,
  "refunded_value": 0,
  "dispute": null,
  "customer": null,
  "payer": null,
  "checkout_url": "https://pay.payzoi.com/c/ch_test_Svy9MIb066RlzlC",
  "return_url": "https://yourstore.com/thanks",
  "webhook_url": "https://yourstore.com/webhooks/payzoi",
  "metadata": { "utm_source": "google" },
  "livemode": false,
  "created_at": "2026-09-24T16:07:30.946Z",
  "expires_at": "2026-09-24T17:07:30.946Z",
  "paid_at": null
}

customer is what you sent in the request (or null). payer is who actually paid, filled in when the charge is paid. paid_value is what the customer paid, the same as value. When paid, payment identifies the payment itself: { "id": "py_…", "code": "PZ-7K3M-9Q2X", "acquirer_reference": "…" }. The code is also shown to the customer on the confirmation screen, so when a customer contacts you about a payment, search for it with payment_code in List charges or in your dashboard. acquirer_reference is the acquirer's own ID for this payment (for Pix, the ID at the Pix provider). Give it to support if you need to trace a payment. refunded_value is how much was refunded, and dispute is null or { "status": "open" | "won" | "lost", "amount", "reason", "respond_by", "opened_at", "closed_at" }. When the charge is paid, fee is our fee and net_value is what goes to your balance (paid_value minus fee). payment_methods in the response is what the checkout will actually offer.

Save the id with your order. If the customer comes back after the charge expires, create a new one.

In live mode, the payment methods depend on your account. If cards aren't enabled for it, asking for "credit_card" only returns 403 card_payments_unavailable; asking for both creates the charge with Pix only. A live Pix charge below R$ 5,00 returns 422 amount_too_low.

Send the customer to pay

Redirect the customer to checkout_url, or send it as a link by email or WhatsApp.

CardThe customer enters the card, charged in a single payment. If the card is declined, they can try another card or Pix.
PixWe show a QR code and a copy-and-paste code, valid for 15 minutes (never past the charge's expires_at). The page updates by itself when the payment is confirmed. While a code is valid, opening the checkout again shows the same code and card payment is blocked, so the customer can't pay twice.
PaidWe show a confirmation and, after 5 seconds, send the customer to your return_url.
Canceled“Back to your store”, at the top left, sends the customer to your return_url. Without one, it goes back to the page they came from. The charge stays pending: the customer can open checkout_url again and pay until it expires.

We add these query parameters to your return_url:

https://yourstore.com/thanks?charge_id=ch_test_Svy9MIb066RlzlC&external_id=123&status=paid
https://yourstore.com/thanks?charge_id=ch_test_Svy9MIb066RlzlC&external_id=123&status=canceled

status=canceled only says the customer left the checkout; it isn't a charge status. Anyone can type these parameters, so use them only to show a message. Confirm the payment with the webhook or Get a charge.

Webhooks

We send a POST with a JSON body to the charge's webhook_url, or to the URL in Dashboard → Integration if the charge doesn't have one.

charge.paidThe payment was confirmed. Release the order.
charge.payment_failedA card was declined. The charge stays pending and the customer can try again, so don't cancel the order.
charge.expiredThe charge expired without a payment. This is final: the charge can't be paid anymore, so you can cancel the order.
charge.canceledYou canceled the charge with Cancel a charge. Final, like charge.expired.
charge.refundedA refund of a paid charge was completed. refunded_value is the total refunded so far; it can be less than paid_value for a partial refund.
charge.dispute_openedThe customer disputed a card payment with their bank. The amount is held from your balance. Consider holding the delivery while it's open.
charge.dispute_won / charge.dispute_lostThe dispute was decided. Won: the money is back in your balance. Lost: it stays out.

data is the full charge, the same object you get from Get a charge:

{
  "id": "evt_7TxvAlzfTmDMGIg",
  "event": "charge.paid",
  "created_at": "2026-09-24T16:07:32.346Z",
  "data": {
    "id": "ch_test_Svy9MIb066RlzlC",
    "status": "paid",
    "value": 5000,
    "paid_value": 5000,
    "fee": 450,
    "net_value": 4550,
    "external_id": "123",
    "payment_method": "credit_card",
    "payment": { "id": "py_Qm3kP0aZt8uX", "code": "PZ-7K3M-9Q2X", "acquirer_reference": "AUTH-481516" },
    "card": { "brand": "visa", "last4": "4242" },
    "payer": { "name": "Ana Souza", "email": "[email protected]", "document": null },
    "paid_at": "2026-09-24T16:07:32.345Z",
    ...
  }
}

For Pix, payment_method is pix, card is null, and payer has the name and CPF or CNPJ. payer.email is the email you sent in customer, or null. For charge.payment_failed, last_decline_reason is card_declined, insufficient_funds, expired_card or processing_error.

Respond and handle retries

Reply with any 2xx status within 10 seconds. Otherwise we retry, with growing intervals, for up to 3 days. Each attempt is signed again at the moment it's sent, so retries pass the 5-minute check below. The same event can arrive more than once, so skip events whose id you've already processed.

Pix is usually confirmed within seconds of the payment. If the customer generated a Pix code, we only send charge.expired after that code can no longer be paid and its confirmation window has closed, so a charge.paid never arrives after charge.expired or charge.canceled.

Verify the signature

Every webhook has a PayZoi-Signature header, so you know it came from us:

PayZoi-Signature: t=1790266052,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
  1. Take t (Unix time in seconds) and v1 from the header.
  2. Compute an HMAC-SHA256 of t + "." + raw request body with your webhook secret (in Dashboard → Integration), as hex.
  3. Accept only if it equals v1 and t is less than 5 minutes old. t is the time of this delivery attempt, not of the payment.

Use the raw body exactly as received. Parsing and re-encoding the JSON changes it and breaks the signature. In Express, declare the webhook route before app.use(express.json()), or that parser reads the body first.

// Express. express.raw keeps the body exactly as received.
const crypto = require("crypto");

app.post("/webhooks/payzoi", express.raw({ type: "application/json" }), (req, res) => {
  const header = req.get("PayZoi-Signature") || "";
  const parts = Object.fromEntries(header.split(",").map(p => p.split("=")));
  const expected = crypto
    .createHmac("sha256", process.env.PAYZOI_WEBHOOK_SECRET)
    .update(`${parts.t}.${req.body}`)
    .digest("hex");
  const received = String(parts.v1 || "");
  const valid = received.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received));
  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
  if (!valid || !fresh) return res.status(400).send("Invalid signature");

  const { id, event, data } = JSON.parse(req.body);
  // Skip if you already processed this event id
  if (event === "charge.paid") {
    // markOrderPaid(data.external_id, data.paid_value)
  } else if (event === "charge.expired") {
    // cancelOrder(data.external_id)
  }
  res.sendStatus(200);
});

Complete example

A store in one file, with Node.js and Express: a product page, the purchase, the checkout and the webhook. It covers what a real integration needs:

  • The price comes from your catalog on the server. The page only sends the product ID.
  • Each purchase creates an order first. Its ID goes to the charge as external_id and as the Idempotency-Key.
  • The order is marked paid only by a signed charge.paid webhook, or by Get a charge, and only when paid_value equals the order total.
  • The thanks page checks the charge itself when the customer comes back before the webhook.
// store.js: a complete store with PayZoi, in one file. Node.js 18+ and Express.
//   npm install express
//   PAYZOI_SECRET_KEY=sk_test_... PAYZOI_WEBHOOK_SECRET=whsec_... STORE_URL=https://yourstore.com node store.js
const express = require("express");
const crypto = require("crypto");

const API = "https://api.payzoi.com/v1";
const STORE_URL = process.env.STORE_URL;   // your site's public address
const app = express();

// Your catalog, with prices in cents. The price always comes from here, never from the browser.
const PRODUCTS = {
  "blue-tshirt": { name: "Blue T-shirt", price: 7990 },
  "coffee-mug": { name: "Coffee mug", price: 3450 }
};
const orders = new Map();      // use your database
const seenEvents = new Set();  // also in your database

async function payzoi(path, options = {}) {
  const res = await fetch(API + path, {
    ...options,
    headers: {
      "Authorization": "Bearer " + process.env.PAYZOI_SECRET_KEY,
      "Content-Type": "application/json",
      ...options.headers
    }
  });
  const body = await res.json().catch(() => null);
  if (!res.ok) throw new Error(body?.error?.message || `PayZoi returned HTTP ${res.status}`);
  return body;
}

// Marks the order paid only if the charge is paid with the order's full total.
function settle(order, charge) {
  if (charge.status === "paid" && charge.paid_value === order.total) order.status = "paid";
  if (["expired", "canceled"].includes(charge.status) && order.status !== "paid") order.status = "canceled";
}

// 1. Webhook. Declared before any JSON body parser: the signature needs the raw body.
app.post("/webhooks/payzoi", express.raw({ type: "application/json" }), (req, res) => {
  const header = req.get("PayZoi-Signature") || "";
  const parts = Object.fromEntries(header.split(",").map(p => p.split("=")));
  const expected = crypto
    .createHmac("sha256", process.env.PAYZOI_WEBHOOK_SECRET)
    .update(`${parts.t}.${req.body}`)
    .digest("hex");
  const received = String(parts.v1 || "");
  const valid = received.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received));
  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
  if (!valid || !fresh) return res.status(400).send("Invalid signature");

  const { id, data } = JSON.parse(req.body);
  if (!seenEvents.has(id)) {
    seenEvents.add(id);
    const order = orders.get(data.external_id);
    if (order) settle(order, data);
  }
  res.sendStatus(200);
});

app.use(express.urlencoded({ extended: false }));

// 2. Product page: each button sends only the product ID.
app.get("/", (req, res) => {
  const items = Object.entries(PRODUCTS).map(([id, p]) =>
    `<form method="post" action="/buy">
       <b>${p.name}</b> R$ ${(p.price / 100).toFixed(2).replace(".", ",")}
       <button name="product" value="${id}">Buy</button>
     </form>`).join("");
  res.send(`<h1>My store</h1>${items}`);
});

// 3. Buy: create the order, then a charge for its total, and send the customer to pay.
app.post("/buy", async (req, res) => {
  const product = PRODUCTS[req.body.product];
  if (!product) return res.status(404).send("Product not found");
  const orderId = crypto.randomUUID();
  const order = { product: req.body.product, total: product.price, status: "pending" };
  orders.set(orderId, order);
  try {
    const charge = await payzoi("/charges", {
      method: "POST",
      headers: { "Idempotency-Key": orderId },
      body: JSON.stringify({
        value: order.total,
        description: product.name,
        external_id: orderId,
        return_url: `${STORE_URL}/thanks`,
        webhook_url: `${STORE_URL}/webhooks/payzoi`
      })
    });
    order.chargeId = charge.id;
    order.checkoutUrl = charge.checkout_url;
    res.redirect(303, charge.checkout_url);
  } catch (err) {
    console.error(err);
    res.status(502).send("We couldn't start the payment. Try again in a moment.");
  }
});

// 4. Back from the checkout. If the webhook hasn't arrived yet, ask PayZoi for the charge.
app.get("/thanks", async (req, res) => {
  const order = orders.get(String(req.query.external_id));
  if (!order) return res.redirect("/");
  if (order.status === "pending" && order.chargeId) {
    try { settle(order, await payzoi(`/charges/${order.chargeId}`)); } catch (err) { console.error(err); }
  }
  const name = PRODUCTS[order.product].name;
  if (order.status === "paid") return res.send(`<h1>Thank you!</h1><p>Your ${name} is paid.</p>`);
  if (order.status === "canceled") return res.send(`<h1>Order canceled</h1><p><a href="/">Back to the store</a></p>`);
  if (req.query.status === "canceled") {
    return res.send(`<h1>Payment not finished</h1><p><a href="${order.checkoutUrl}">Go back to the payment</a></p>`);
  }
  res.send(`<h1>Waiting for payment</h1><p>If you already paid, refresh this page in a few seconds.</p>`);
});

app.listen(process.env.PORT || 4000);

Run it with your test key

  1. Save the file as store.js and run npm install express.
  2. Start it with your test key, your webhook secret (both in Dashboard → Integration) and the public https address of your site in STORE_URL. On your computer, a tunnel such as cloudflared tunnel --url http://localhost:4000 gives you one.
  3. Open the store, buy a product and pay on the checkout with the test card. You come back to /thanks with the order paid, and the sale shows in your dashboard.

To go live, swap in your sk_live_ key once your account is activated for live payments. Nothing else changes.

Get a charge

GET/v1/charges/{id}
curl https://api.payzoi.com/v1/charges/ch_test_Svy9MIb066RlzlC \
  -H "Authorization: Bearer sk_test_your_secret_key"

Returns the same object as Create a charge, with the current status:

pendingWaiting for payment. If a card was declined, last_decline_reason says why.
paidPaid. paid_value, payment_method ("credit_card" or "pix"), payer and paid_at are filled in. For card payments, card has the brand and last4; for Pix it's null.
expiredExpired without a payment. Final.
canceledYou canceled it. Final.

A charge only moves from pending to one of the other three, and never changes after that. A customer leaving the checkout doesn't change the status; the charge stays pending until it's paid, expires or you cancel it.

List charges

GET/v1/charges
statusquerypending, paid, expired or canceled.
external_idqueryOnly charges with this external_id, to find the charges of an order.
payment_codequeryThe charge paid with this payment code, like PZ-7K3M-9Q2X (the code the customer sees).
created_fromqueryCreated on or after this date, in UTC, like 2026-09-01 or 2026-09-01T12:00:00Z.
created_toqueryCreated on or before this date, in UTC. A date without time includes the whole day (until 23:59:59 UTC).
pagequeryPage number. Default 1.
per_pagequery1 to 100. Default 20.
curl "https://api.payzoi.com/v1/charges?status=paid&created_from=2026-09-01&per_page=50" \
  -H "Authorization: Bearer sk_test_your_secret_key"
{
  "data": [ { "id": "ch_test_Svy9MIb066RlzlC", "status": "paid", ... } ],
  "page": 1,
  "per_page": 50,
  "total": 1
}

Newest first. Test and live charges are listed separately, according to the key. To page through a period without charges shifting between pages, set created_to to the moment you start and keep it on every page.

Cancel a charge

POST/v1/charges/{id}/cancel

Use it when the order is canceled on your side, so the customer can't pay it anymore. The charge becomes canceled and we send charge.canceled. Canceling a charge that is already canceled returns it again.

curl -X POST https://api.payzoi.com/v1/charges/ch_test_Svy9MIb066RlzlC/cancel \
  -H "Authorization: Bearer sk_test_your_secret_key"
409 charge_paidThe charge is already paid.
409 charge_expiredThe charge already expired.
409 pix_pendingThe customer has a Pix code that can still be paid. The message says until when; try again after that.
409 payment_in_progressThe customer is paying this charge right now. Try again in a minute.

Refunds and disputes

Refunds and disputes are handled in your dashboard, not through the API. To refund, open Transactions and click Refund on the payment (full or partial). When a customer disputes a card payment with their bank (a chargeback), it shows up under Disputes, where you send your side. Your system is told about both by webhook: charge.refunded, charge.dispute_opened, charge.dispute_won and charge.dispute_lost, with refunded_value and dispute filled in on the charge.

Balance and account

GET/v1/balance
{ "available": 128400, "pending": 45900, "in_withdrawal": 0, "currency": "brl", "livemode": true }
availableYour balance available for transfer, after fees and minus refunds in progress. It can be negative after a refund or a lost dispute; your next sales cover it.
pendingPaid card sales, after fees, still inside the holding period set for your account. They move to available when it ends. Pix sales go straight to available.
in_withdrawalTransfers in progress. Already taken out of available.
in_refundRefunds requested and not completed yet. Already taken out of available.

With a live key, these are your real balances. With a test key you get the test balance: your test payments after fees, minus test refunds, with livemode: false. Test sales count right away (pending is always 0), and test money can never be withdrawn. Transfers of your real balance are arranged with PayZoi support.

GET/v1/account
{
  "id": "acct_0VpcprpbriM",
  "name": "Northwind Studio",
  "email": "[email protected]",
  "status": "approved",
  "live_enabled": true,
  "webhook_url": "https://yourstore.com/webhooks/payzoi",
  "livemode": false
}

Testing

Your sk_test_ key works as soon as you send your business details in the dashboard, under Pending. Test mode behaves like live mode: same endpoints, same checkout, same webhooks and statuses. Test payments never reach a bank and no real money moves.

Card

On a test checkout the card fields are locked and the PayZoi test card is shown above them. Tap it: it fills in 4242 4242 4242 4242, expiry 12/30 and CVC 123, and Pay completes the test purchase, approved.

Pix

Generate the code with any valid CPF or CNPJ, like 123.456.789-09. The test code can't be paid in a bank app: it's marked as paid by itself about 20 seconds after it's generated, or right away with Simulate the payment on the checkout.

What you can check

  • The charge.paid webhook, signed with your webhook secret.
  • The charge in Get a charge and List charges, and in your dashboard.
  • The test balance in your dashboard and in GET /v1/balance: each test sale after fees.
  • Refunds of test payments, done in your dashboard: they complete right away and send charge.refunded.

On the Pix tab of a test checkout, Fill with test data fills in a name and a test CPF.

Errors

Errors return an HTTP status and a JSON body with a code and a message that says how to fix the request.

400 invalid_jsonThe body isn't valid JSON.
401 unauthorizedThe secret key is missing or not valid.
403 account_pendingYour account setup isn't finished. Send your business details in the dashboard, under Pending: test mode turns on right away. Applies to test and live keys.
403 account_rejectedYour account isn't authorized to process payments.
403 account_suspendedYour account is suspended. Contact support.
403 live_mode_disabledLive payments aren't enabled for your account yet. Keep using your test key.
403 card_payments_unavailableYou asked for card only, but cards aren't enabled for live payments on your account. Include "pix".
404 not_foundNo charge with this ID for this key (test and live charges are separate), or the endpoint doesn't exist.
413 payload_too_largeThe request body is over 100 kB.
409 charge_paid, charge_expired, pix_pending, payment_in_progressThe charge can't be canceled now. See Cancel a charge.
422 amount_too_lowA live Pix charge needs at least 500 (R$ 5,00).
422 invalid_requestA field is missing or wrong. message names the first problem and problems lists all of them.
500 server_errorSomething failed on our side. Try again; if it keeps happening, contact support.
{
  "error": {
    "code": "invalid_request",
    "message": "value is required: an integer in cents, minimum 50 (R$ 0,50)",
    "problems": [
      "value is required: an integer in cents, minimum 50 (R$ 0,50)"
    ]
  }
}

Integrations built on the earlier POST /v1/checkout/sessions endpoint keep working. New integrations should use /v1/charges.