Payments

Most integrations use Checkout Sessions and never touch the Payments API directly. Use it when your platform renders its own UI (mobile, in-terminal, back-office) and already knows the method — the headless mode.

Create a Payment directly

request
POST /api/v1/payments
Authorization: Bearer sk_test_...
Idempotency-Key: your-deposit-id

{
  "amount": "500.00",
  "asset_code": "USDT.TRC20",
  "payment_method": "crypto",
  "country": "AE",
  "customer": "cust_01J9ZR2KD7VE",
  "metadata": { "crm_transaction_id": "DEP-448901" }
}
response
201 Created

{
  "id": "pay_01J9ZX8KT2M1",
  "object": "payment",
  "status": "requires_action",
  "amount": "500.00",
  "asset_code": "USDT.TRC20",
  "payment_method": "crypto",
  "country": "AE",
  "customer": "cust_01J9ZR2KD7VE",
  "provider_account": "pacc_01J8XN5QW9BC",
  "next_action": {
    "type": "crypto_deposit",
    "deposit_address": "TWd4gh8N1tK9qkGf2vX7...",
    "network": "TRC20",
    "expires_at": "2026-07-05T11:41:22Z"
  },
  "metadata": { "crm_transaction_id": "DEP-448901" },
  "created_at": "2026-07-05T09:41:22Z"
}

Open-amount works the same as sessions: omit amount, send "open_amount": true. asset_code stays required and the two are mutually exclusive.

Card payments (Stripe)

Card is available via Stripe: send "payment_method": "card" with a fiat asset_code (USD and EUR to start). Card details are entered only on Stripe's own surfaces — PayXiro never sees the card number. Each Stripe provider account picks one of two presentations:

Redirect (default) — next_action.type is redirect; send the customer to redirect_url (Stripe's hosted Checkout page).

Embeddednext_action.type is provider_component, carrying { "provider": "stripe", "client_secret", "publishable_key" }. The hosted Cashier mounts Stripe's embedded Checkout in place; a headless client loads Stripe.js and calls stripe.initEmbeddedCheckout({ clientSecret }).

As always, credit only on the signed webhook — never on the browser return.

The Payment state machine

These are the only Payment states. requires_action waits on the customer or a provider action; under_review waits on a risk decision and resolves to processing or failed.

StatusMeaning
createdPayment recorded, not yet routed to a provider action
requires_actionWaiting on the customer or a provider redirect/action (e.g. send crypto to an address)
under_reviewHeld for a risk decision (rule outcome or the manual review queue)
processingProvider is settling; awaiting confirmation
succeededTerminal — funds confirmed; the credit-worthy signal
failedTerminal — declined, routing_exhausted, or a risk block
cancelledTerminal — withdrawn before processing
expiredTerminal — the action window elapsed with no completion

Legal transitions: created → requires_action | under_review | processing | failed | cancelled | expired; requires_action → under_review | processing | cancelled | expired; under_review → processing | failed; processing → succeeded | failed. Every transition is recorded on the transaction timeline and emitted as a payment.* event.

Manual methods: submit-form

For manual methods (e.g. manual bank), post the dynamic-form values the method defines:

request
POST /api/v1/payments/{id}/submit-form
Authorization: Bearer sk_test_...

The values are validated against the method's form_definition, stored as form_values, and the Payment transitions requires_action → under_review. This call is not money-creating (no Idempotency-Key) and is idempotent on state. Fetch the method's form definition first — fields are data, not code.

Poll status (reconciliation fallback)

If a webhook is delayed or missed, reconcile by fetching the Payment:

request
GET /api/v1/payments/{id}
Authorization: Bearer sk_test_...

Returns 404 (never 403) if the Payment is unknown or belongs to another tenant. Poll as a scheduled sweep over Payments still pending after N minutes — not as the primary channel. When a poll shows succeeded, run the same idempotent credit path the webhook uses (keyed on payment.id), so a poll and a late webhook cannot double-credit.

See Webhooks for the source-of-truth channel.

View this page as markdown·Sourced from the PayXiro engineering docs — the contract wins on any technical claim.