Skip to main content
This is the shortest path for a B2B integrator/platform: onboard your sellers once, 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

  • Onboards your sellers (KYB + PIX) so each is paid on its own account.
  • Accepts a payment intent — for one of your sellers — 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 — Onboard a seller

Every payment settles to one of your sellers. Onboard each seller once — KYB + PIX — via /v1/accounts, addressed by your own externalId:
In test mode seller KYB auto-approves. Poll the account (or subscribe to connected_account.ready_to_collect) until readyToCollect: true — that’s when it can be invoiced. Full walkthrough: Sellers & buyers.

Step 3 — Register a webhook subscription

Before you create your first intent, subscribe to the payment_intent.* (and optionally connected_account.*) events you care about. The signing secret is also returned exactly once.
See Webhooks for the signature format, retry policy, and language-specific verifier examples.

Step 4 — Create a payment intent for the seller

Each intent collects a specific amount from a buyer for one of your sellerssellerExternalId is required. externalId is your own primary key — re-POSTing the same value is safe.
The response carries the field you actually need:
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 6 — 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/payments/{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

  • Invoicing a seller that isn’t readyToCollect. Onboard the seller (KYB + PIX) first; sellerExternalId must reference an approved, payout-configured seller.
  • 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.