Skip to main content
This is the shortest path for a B2B integrator: keep your own ERP / invoicing, hand Caratuva the payment + KYC + cross-border settlement, and reconcile through webhooks. The full happy path takes minutes against test mode and is identical against live.

What Caratuva does on this surface

  • Accepts a payment intent from your server.
  • Returns a hosted payment URL you forward to your buyer.
  • Onboards the buyer through KYC (mandatory — no skip path).
  • Collects payment from the buyer, runs cross-border transfer + FX, and pays out BRL via PIX to the seller’s registered destination.
  • Notifies your ERP through signed outbound webhooks at every state change.
You keep the customer relationship, the order management, and the line-item authority. Caratuva keeps the regulatory and settlement plumbing.

Step 1 — Issue an API key

Log in to the dashboard and create a pk_test_ key for development, plus a pk_live_ key for production. Store both in your secret manager. The plaintext secret is surfaced exactly once at creation time. See API keys for the full CRUD surface.

Step 2 — Register a webhook subscription

Before you create your first intent, subscribe to the payment_intent.* events you care about. The signing secret is also returned exactly once.
curl -X POST https://api.caratuva.com/v1/webhook-subscriptions \
  -H "X-API-Key: $CARATUVA_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "url": "https://erp.example.com/webhooks/caratuva",
    "eventTypes": [
      "payment_intent.created",
      "payment_intent.buyer_kyc_approved",
      "payment_intent.on_chain_confirmed",
      "payment_intent.settled",
      "payment_intent.failed",
      "payment_intent.cancelled",
      "payment_intent.expired"
    ]
  }'
See Webhooks for the signature format, retry policy, and language-specific verifier examples.

Step 3 — Create a payment intent

Each intent is a request to collect a specific amount from a specific buyer. externalId is your primary key — re-POSTing the same value is safe.
curl -X POST https://api.caratuva.com/v1/payment-intents \
  -H "X-API-Key: $CARATUVA_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "content-type: application/json" \
  -d '{
    "externalId": "INV-2026-00042",
    "amount": "12500.00",
    "currency": "USD",
    "buyer": { "email": "buyer@acme.com", "name": "ACME Imports LLC", "country": "US" },
    "returnUrl": "https://erp.example.com/orders/42/paid",
    "metadata": { "orderId": "42" }
  }'
The response carries the field you actually need:
{
  "id": "ckabc...",
  "status": "awaiting_buyer",
  "hostedPaymentUrl": "https://pay.caratuva.com/r/ckpub..."
}
See Payment intents for the full request and response schema. Email, SMS, or render the hostedPaymentUrl in your own UI — whichever channel you already use to engage your buyer. Caratuva does not auto-email the buyer on the payment-intent surface, because you own the customer relationship. The hosted page handles email magic-link sign-in, KYC, FX quoting, and payment collection. Your buyer never leaves pay.caratuva.com until the redirect to returnUrl after settlement.

Step 5 — Reconcile through webhooks

Every state change fires a payment_intent.* event. The two events that matter for most ERPs:
  • payment_intent.settled — funds are in the seller’s BRL account. Mark the order paid in your system. The payload carries paymentIntentId, externalId, and the original metadata (plus source and externalEventId). For settlement amounts (amount, currency, brlSettledAmount, fxRateAtSettlement), fetch GET /v1/payment-intents/{id} on receipt — they are not on the webhook body.
  • payment_intent.failed — terminal failure. The payload carries the reason. Refund the order or surface an error to your buyer.
Use X-Caratuva-Delivery-Id as an idempotency key — Caratuva retries up to seven times with exponential backoff (immediate, 30s, 2m, 10m, 1h, 6h, 24h) and your endpoint will see the same delivery id on every attempt.

Test mode

pk_test_ keys run the entire flow end-to-end against a sandbox payment instance. The same intent transitions, the same webhooks, the same response shapes — but KYB and buyer KYC auto-approve and no real money moves. Use it for CI and staging. See Environments.

Common pitfalls

  • Missing buyer email. It’s required and must be a real address — that’s where the magic-link goes.
  • Sending floats. Pass amount as a string ("12500.00") to avoid float drift.
  • Re-serializing the webhook body before verifying. The signature is computed over the raw bytes. Always verify against req.rawBody, not JSON.stringify(req.body).
  • Reusing Idempotency-Key across distinct logical operations. It’s scoped per organization and collapses retries; don’t share it across unrelated intents.

Where to next

  • Payment intents — full schema, error codes, state machine.
  • Webhooks — signature verification, retry policy, payment_intent.* event reference.
  • API keys — issuance, rotation, revocation.