Skip to main content
These are the issues that appear most often during integration. Each entry covers the symptom, root cause, and the exact change needed to resolve it.

1. 401 INVALID_CREDENTIALS — Wrong API keys

Symptom Every API call returns 401 INVALID_CREDENTIALS, even when credentials appear to be set correctly. Root cause Sandbox and production credentials are completely separate. Using a production private-secret-key against https://api-sandbox.y.uno/v1 (or vice versa) will always return 401 — there is no fallback or cross-environment validation. This is also triggered by copy-paste errors that include trailing whitespace or line breaks. Fix
  1. Open Dashboard > Settings > API Keys.
  2. Confirm you are viewing the correct environment tab (Sandbox or Production).
  3. Copy public-api-key, private-secret-key, and account-code fresh from the dashboard.
  4. Ensure the values are trimmed — no leading/trailing whitespace.
  5. Confirm the base URL matches the environment: https://api-sandbox.y.uno/v1 for sandbox.
# Verify credentials against the correct environment
curl -X GET https://api-sandbox.y.uno/v1/payments \
  -H "public-api-key: YOUR_SANDBOX_PUBLIC_KEY" \
  -H "private-secret-key: YOUR_SANDBOX_PRIVATE_KEY" \
  -H "account-code: YOUR_ACCOUNT_CODE"
Never use public-api-key alone for server-side API calls. Payment creation and management endpoints require private-secret-key.

2. 403 AUTHORIZATION_REQUIRED — Missing account-code header

Symptom The request returns 403 AUTHORIZATION_REQUIRED even though public-api-key and private-secret-key are correct. Root cause Yuno requires three headers for authenticated requests: public-api-key, private-secret-key, and account-code. The account-code identifies which merchant account to operate against and is mandatory. Omitting it, or passing an account-code value that does not match the credentials’ associated account, causes a 403. Fix Add account-code to every API request header. The value is found in Dashboard > Settings > API Keys alongside the other credentials.
const response = await fetch('https://api-sandbox.y.uno/v1/payments', {
  method: 'POST',
  headers: {
    'public-api-key': process.env.YUNO_PUBLIC_KEY,
    'private-secret-key': process.env.YUNO_PRIVATE_KEY,
    'account-code': process.env.YUNO_ACCOUNT_CODE,  // required
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(payload),
});

3. 400 CHECKOUT_SESSION_NOT_FOUND — Session expired

Symptom The SDK initialization call or a payment attempt returns 400 CHECKOUT_SESSION_NOT_FOUND, often after the user has been idle on the checkout page. Root cause Checkout sessions have a 60-minute TTL from the time of creation. Once expired, the session token is permanently invalid and cannot be reused or refreshed. This most commonly occurs in testing (leaving a tab open overnight) or in production flows where users return to a page after an extended absence. Fix
  1. Detect the CHECKOUT_SESSION_NOT_FOUND error in your error handler.
  2. Call your backend to create a new checkout session via POST /v1/checkout-sessions.
  3. Re-initialize the Yuno SDK with the new session token.
async function initializeCheckout() {
  // Always create a fresh session — do not cache tokens across page loads
  const session = await fetch('/api/create-checkout-session', {
    method: 'POST',
  }).then(res => res.json());

  const yuno = await Yuno.initialize(process.env.YUNO_PUBLIC_KEY);
  yuno.startCheckout({
    checkoutSession: session.checkout_session,
    // ...
  });
}
Create the checkout session server-side at page load time rather than caching it. This ensures the token is always fresh when the user reaches the payment step.

4. Payment stuck on CREATED — continuePayment() not called after 3DS

Symptom A payment is created successfully and returns a CREATED status, but the payment never progresses to PENDING or SUCCEEDED. The Yuno Dashboard shows the payment as CREATED indefinitely. Root cause For payment flows that require 3D Secure authentication, the SDK presents the 3DS challenge inside an iframe or redirect. After the user completes authentication, you must call yuno.continuePayment() to submit the authentication result to Yuno. If this call is omitted, the payment sits in CREATED with no further action taken — Yuno cannot advance the payment without confirmation that authentication completed. Fix Implement the onPaymentMethodSelected and yunoCreatePayment callbacks, and call continuePayment() once your backend has created the payment.
const yuno = await Yuno.initialize(process.env.YUNO_PUBLIC_KEY);

yuno.mountCheckoutLite({
  checkoutSession: session.checkout_session,
  countryCode: 'BR',
  language: 'en',
  showPayButton: false,

  yunoCreatePayment: async (oneTimeToken) => {
    // Create payment on your backend
    await fetch('/api/payments', {
      method: 'POST',
      body: JSON.stringify({ one_time_token: oneTimeToken }),
    });

    // This call is mandatory — without it, 3DS flows never complete
    yuno.continuePayment();
  },

  yunoPaymentResult: (result) => {
    console.log('Payment result:', result);
  },
});

5. SDK not loading — public-api-key not passed to Yuno.initialize()

Symptom Yuno.initialize() throws an error or the SDK renders a blank checkout. The browser console may show TypeError: Cannot read properties of undefined or a 401 error on SDK asset requests. Root cause Yuno.initialize() requires the public-api-key as its first argument. This key is used to authenticate SDK asset loading and to scope the checkout session to your merchant account. Passing undefined, an empty string, or the private-secret-key by mistake causes the SDK to fail at initialization before any UI is rendered. Fix Pass the public-api-key explicitly and verify it is defined before calling initialize.
import { Yuno } from '@yuno-payments/sdk-web';

const PUBLIC_KEY = process.env.YUNO_PUBLIC_KEY;

if (!PUBLIC_KEY) {
  throw new Error('YUNO_PUBLIC_KEY environment variable is not set');
}

// public-api-key is required as the first argument
const yuno = await Yuno.initialize(PUBLIC_KEY);
Never pass private-secret-key to Yuno.initialize(). The public key is designed for client-side use. The private key must remain server-side only.

6. CORS error in browser — calling private endpoints from the browser

Symptom The browser console shows a CORS error such as Access to fetch at 'https://api-sandbox.y.uno/v1/payments' from origin 'http://localhost:3000' has been blocked by CORS policy. Root cause Yuno’s private API endpoints (payment creation, capture, refund, etc.) are intended for server-to-server communication and do not return CORS headers that permit browser-origin requests. Calling these endpoints directly from frontend JavaScript exposes your private-secret-key in the browser and will fail with a CORS error in any case. Fix Move all calls that use private-secret-key to your backend. Your frontend should only call your own API, which then calls Yuno server-side.
Browser → Your Backend API → Yuno API

       private-secret-key lives here only
// ❌ Wrong — never call Yuno private endpoints from the browser
const payment = await fetch('https://api-sandbox.y.uno/v1/payments', {
  headers: { 'private-secret-key': '...' }, // key exposed in browser
});

// ✅ Correct — proxy through your own backend
const payment = await fetch('/api/create-payment', {
  method: 'POST',
  body: JSON.stringify({ amount: 100, currency: 'BRL' }),
});

7. 400 INVALID_PARAMETERS on customer.document — PIX requires CPF/CNPJ

Symptom A PIX payment returns 400 INVALID_PARAMETERS with a message referencing customer.document.document_number or customer.document.document_type. Root cause PIX is a Brazilian payment method regulated by the Banco Central do Brasil. All PIX transactions require the payer’s Brazilian tax identification: CPF (11 digits, for individuals) or CNPJ (14 digits, for businesses). These fields are mandatory — the payment cannot be created without them. Passing a formatted document number (with ., -, or / characters) also triggers this error; only raw digits are accepted. Fix Include the customer.document object with unformatted digits only.
{
  "payment_method": {
    "token": "{{one_time_token}}",
    "type": "PIX"
  },
  "amount": {
    "value": 150.00,
    "currency": "BRL"
  },
  "country": "BR",
  "customer": {
    "document": {
      "document_type": "CPF",
      "document_number": "12345678901"
    },
    "email": "customer@example.com",
    "first_name": "Ana",
    "last_name": "Silva"
  }
}
document_type must be "CPF" (11 digits) for individuals or "CNPJ" (14 digits) for businesses. Do not include formatting characters — pass digits only.

8. IDEMPOTENCY_DUPLICATED — X-Idempotency-Key already used

Symptom A payment creation request returns 400 IDEMPOTENCY_DUPLICATED. Root cause Yuno’s idempotency system stores the association between an X-Idempotency-Key value and the original request payload. If you send a new request with the same key but a different payload, Yuno rejects it to prevent accidental double payments. This commonly happens when a key is incorrectly derived from non-unique data (e.g., a user ID or product ID rather than a unique transaction ID) or when a key is reused across retry attempts that changed the request body. Fix Generate a new UUID v4 for each distinct payment intent. Only reuse the same key if you are retrying the exact same request (same payload) after a network failure.
import { v4 as uuidv4 } from 'uuid';

const response = await fetch('https://api-sandbox.y.uno/v1/payments', {
  method: 'POST',
  headers: {
    'private-secret-key': process.env.YUNO_PRIVATE_KEY,
    'public-api-key': process.env.YUNO_PUBLIC_KEY,
    'account-code': process.env.YUNO_ACCOUNT_CODE,
    'X-Idempotency-Key': uuidv4(), // unique per payment intent
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(payload),
});
Store the idempotency key alongside the payment intent in your database. On retry after a network error, reuse the stored key with the identical payload — this guarantees at-most-once processing without creating a duplicate.