Skip to main content
POST
/
payment-intents
Create Payment Intent
curl --request POST \
  --url https://api.zopay.cash/connect/v1/payment-intents \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "amount": 1,
  "currencies": [
    "<string>"
  ],
  "external_ref": "<string>",
  "description": "<string>",
  "metadata": {}
}
'
{}
Mint a payment intent for the partner org. Idempotency: the Idempotency-Key header is required for this endpoint (enforced by idempotency_required). Same key + same body within 24h replays the original response verbatim. Same key + different body returns 409 idempotency_conflict. Unlike the address-creation flow, the underlying intent create is NOT idempotent on its own (it mints a fresh ref_code per call, so a naive retry would create two intents). The Idempotency-Key store is the only safety net here — without the header a retry mints a second intent. Partners SHOULD send the header on every POST. Dependency order: idem is taken before auth so the Idempotency-Key header is parsed (which can 400 on its own) before the DB hit for the scope check. Sandbox routing: sk_test_… keys (auth.mode == "sandbox") are routed to ConnectPaymentIntentsSandboxService, which makes no payment-server / BitGo / Rhino calls and persists the intent with deterministic fake addresses. sk_live_… keys continue to hit the real path through PaymentIntentService.create_payment_link.

Authorizations

Authorization
string
header
required

Paste your Connect API key (sk_live_… for production, sk_test_… for sandbox) without the Bearer prefix. Mint and rotate keys from the admin panel.

Headers

Idempotency-Key
string | null
authorization
string | null

Body

application/json

Request body for POST /connect/v1/payment-intents.

Forbidden extras: typo'd field names surface as a 422 rather than being silently ignored. Partners can recover by checking their request body shape against the docs.

All amount handling is in the intent's primary currency (currencies[0]). Multi-currency intents accept the same nominal amount across every currency -- there is no per-currency pricing slot here. If a partner needs per-currency pricing they can either mint one intent per currency or call a future quote API.

amount
required

Requested amount in the primary currency. Decimal string preferred to avoid float-precision loss.

Required range: x > 0
currencies
string[]
required

Asset codes the intent will accept, in priority order. First entry is the primary currency (drives the merchant-facing amount column). Each currency expands to all active networks server-side.

Required array length: 1 - 10 elements
external_ref
string | null

Partner-supplied attribution key. Stamped on the intent so webhooks and list filters can correlate back to your internal user. Omit if no per-user attribution applies (e.g. tenant-treasury collection page).

Maximum string length: 255
description
string | null

Free-form description, echoed back to the partner.

Maximum string length: 500
metadata
Metadata · object

Free-form JSON object for partner-side bookkeeping. Capped at 50 keys / 500-char values per key.

Response

Successful Response

The response is of type Response Create Payment Intent Payment Intents Post · object.