Skip to content

Developers

POST a charge.Get a webhook.

REST API, per-environment keys and signed webhooks. Test it all in sandbox.

create-payment.sh
curl -X POST https://api.yuvexpay.com/v1/payments \
  -H "Authorization: Bearer ypk_test_ab12cd34ef_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -H "X-Idempotency-Key: order-1234-attempt-1" \
  -d '{
    "amount": 49.90,
    "methods": ["PIX"],
    "currency": "BRL",
    "mode": "headless",
    "description": "Order #1234",
    "externalId": "order-1234",
    "expiresInMinutes": 30,
    "customer": {
      "name": "Pedro Álvares Cabral",
      "email": "[email protected]"
    }
  }'
POST /v1/payments · 201 Created

Verify the signature.Reject the rest.

HMAC-SHA256 over the timestamp and raw body, compared in constant time.

  • Event in the headerX-Webhook-Event. The body has no envelope.
  • At-least-once deliveryDeduplicate by X-Webhook-Delivery-Id.
  • 300 secondsRecommended replay window.
Webhooks guide
verify-webhook.js
import crypto from "crypto";

const SKEW_SECONDS = 300;

function verifyWebhook(rawBody, headers, secret) {
  const ts = headers["x-webhook-timestamp"];
  const sig = headers["x-webhook-signature"];
  if (!ts || !sig) return false;

  const drift = Math.abs(Math.floor(Date.now() / 1000) - parseInt(ts, 10));
  if (drift > SKEW_SECONDS) return false;

  const expected = "v1=" + crypto
    .createHmac("sha256", secret)
    .update(`${ts}.${rawBody}`)
    .digest("hex");

  const a = Buffer.from(sig);
  const b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
  const rawBody = req.body.toString("utf8");
  if (!verifyWebhook(rawBody, req.headers, process.env.WEBHOOK_SECRET)) {
    return res.status(401).send("Invalid signature");
  }

  const eventType = req.headers["x-webhook-event"];
  const deliveryId = req.headers["x-webhook-delivery-id"];
  const body = JSON.parse(rawBody);

  if (alreadyProcessed(deliveryId)) return res.status(200).send("OK");

  switch (eventType) {
    case "PAYMENT_PAID":
      console.log(body.txId, body.amount, body.payer?.document ?? null);
      break;
    case "WITHDRAWAL_SENT":
      console.log(body.txId, body.recipient?.document ?? null);
      break;
    case "WITHDRAWAL_FAILED":
      console.log(body.txId, body.failureReason);
      break;
  }

  res.status(200).send("OK");
});
HMAC-SHA256 · timingSafeEqual

Test without real money.The cents decide.

With a ypk_test_ key, the cents of the amount pick the outcome for Pix, card and boleto.

Charges
CentsOutcome
.01Paid, about 5 s later
.02Stays open
.03Cancelled
.04Expired
.05Paid
OthersPaid
Payouts
CentsOutcome
.01Completed
.02Failed: insufficient funds at the provider
.03Failed: invalid Pix key
.04Stays processing
OthersCompleted

To force a status right away, use POST /v1/payments/{id}/simulate.

Sandbox sends no webhooks. Poll the status or send a test event from the dashboard.

Sandbox guide

The API contract.Same in production.

What holds on every call.

  • Authentication

    Bearer with ypk_test_ or ypk_live_. The prefix picks the environment; the URL stays the same.

  • Scopes

    Each key does only what it may: payments:write, withdrawals:read, balance:read and more.

  • Idempotency

    X-Idempotency-Key on POSTs that create resources. Valid for 24 h; a different body under the same key gets 409.

  • Errors

    A stable error.code, a readable message and per-field details.

  • Rate limits

    1,000 GET, 100 POST and 2,000 total per minute, per company, with X-RateLimit-* headers.

  • Versioning

    The API version is pinned per key. You upgrade when you choose.

The charge lifecycle.No guesswork.

The main path and the possible detours. The relevant ones become webhooks.

Main path

  1. NEW
  2. PROCESSING
  3. CONFIRMED
  4. PAID
  5. PARTIAL_REFUND
  6. REFUNDED

Before payment

  • PENDING_METHOD_SELECTION
  • EXPIRED
  • CANCELLED

After payment

  • CHARGEBACK
  • MED_FROZEN
  • PENDING_METHOD_SELECTION only on hosted checkout.
  • An expired boleto can still become PAID.

Integrate your way.With or without code.

Write it yourself, ask an AI or connect blocks. One API for all of it.

TScheckout.ts

REST API with signed webhooks.

  • Amounts in BRL, like 49.90
  • X-Idempotency-Key: a retry never charges twice
  • Webhooks signed with HMAC-SHA256
  • Sandbox with a ypk_test_ key
Open the docs

Works with

  • Claude Code
  • Cursor
  • Codex
  • Windsurf
  • GitHub Copilot
  • v0
  • Replit
  • n8n
  • Make
  • Zapier

Your next salecan land here.

No monthly fee. No setup. Just a fee per sale.

Every account starts in sandbox, with test keys.