API Specification (YAML)
openapi: 3.0.0
info:
title: CryptoProc API
description: |
API for integrating payment acceptance (SBP, Cards, Cryptocurrency, P2P) on your website.
# 🔐 Authorization (Keys)
In your project settings in the dashboard, you will find two different keys:
- **API Key** — public identifier of the project.
- **Secret Key** (also known as Webhook Secret) — private password for secure server-to-server interaction.
> **Important!** All requests to the API for creating invoices must be authorized using the **Secret Key**. Do not use the API Key for these purposes. Pass the Secret Key in the header: `Authorization: Bearer <Your_Secret_Key>`.
# 👤 Payer data
The banking provider requires the buyer's contact to be sent with the payment.
Pass an email (`client_email`), a phone (`client_phone`) or a Telegram chat id
(`client_telegram_id`) — any one is enough, email is preferred.
For `integration_type: h2h` both a contact and `client_ip` are **required**:
the buyer never opens our page, so you are the only source of that data.
With `standard` we see the payer's address ourselves, `client_ip` is not needed.
> During the transition invoices without these fields are still created:
> a technical address is substituted for the contact. Please update your
> integration — once every merchant is migrated such requests will return 400.
# ↩️ Customer return
After paying, the buyer is sent back to your site. The address is picked in
this order: `success_url` (or `fail_url`) from the create-invoice request →
the address from the project settings → the project website.
Parameters are appended so the order can be identified without calling the API:
```
https://shop.example/thanks?order=777&txn=1426&invoice_id=nI0ivCPL&custom=order-777
```
* `txn` — payment attempt identifier (present before this change);
* `invoice_id` — invoice identifier, the same one `/payment/create` returned;
* `custom` — your order identifier, if you passed one.
> The return is not a payment confirmation: use it to render a thank-you page,
> but release goods based on the webhook or on `GET /payment/status`.
# 🔁 P2P methods
Besides SBP, cards and crypto there are two P2P methods: `p2p_sbp` ("SBP P2P" —
a transfer via SBP or to a phone number) and `p2p_card` ("Card transfer").
The payer transfers the exact amount to payment details issued for this very
payment. The methods are enabled by your manager. Which of them are on for the
project, with which sub-methods and amount limits, is returned by
`GET /payment/methods`.
Sub-method codes are ours and do not depend on the provider that processes
the payment:
| Code | Method | What it is |
|---|---|---|
| `sbp_p2p` | `p2p_sbp` | SBP transfer from any bank |
| `tbank` | `p2p_sbp` | Payment from T-Bank, only from the T-Bank app |
| `vtbbank` | `p2p_sbp` | Payment from VTB, only from the VTB app |
| `ozonbank` | `p2p_sbp` | Payment from Ozon Bank, only from the Ozon Bank app |
| `mobile_topup` | `p2p_sbp` | Mobile phone top-up |
| `p2p_cis` | `p2p_sbp` | Transfer to a CIS country (foreign phone top-up) |
| `card_ru` | `p2p_card` | Card transfer |
Which sub-methods are available right now, for which amounts and with which
payment condition, is in `channels` of the `GET /payment/methods` response:
the set and the limits change.
* **standard** — the response carries `payment_url`; the payer picks a
sub-method on our page and gets the payment details there.
* **h2h** — the payment details come right in the `POST /payment/create`
response (`requisite`, `amount`, `expires_in`, `payer_warning`). Available
only when **H2H for P2P** is enabled for the project — your manager turns it
on; otherwise the request is rejected with 400. The payer contact
(`client_email`, `client_phone` or `client_telegram_id`) and `client_ip` are
always required. Choose a sub-method with `p2p_channel`; without it the
first one that accepts the amount is used.
The payer must transfer exactly `amount` and follow `payer_warning` (for
example, "only from T-Bank"). The details are valid for about 10 minutes; once
they expire, create a new invoice. Confirm the payment by webhook or
`GET /payment/status`, as with the other methods.
# 🧪 Test mode
Lets you verify the whole integration without spending real money. Enabled by
the "Test mode" toggle in the project settings in your dashboard — it is not
controlled through the API.
While the mode is on, the API behaves as usual, but instead of real payment
details (an SBP link or a crypto address) the response carries a link to the
simulation page, and the body gains `test_mode: true`.
How it goes:
1. Turn test mode on in the project settings.
2. Create an invoice with the usual `POST /payment/create` request.
3. Open the returned link and press "Pay" or "Decline".
4. The invoice becomes `paid` or `failed`, a **real notification** is sent to
your `webhook_url`, and the buyer is redirected to `success_url` / `fail_url` —
so the entire chain is checked, including the handling on your side.
5. Once everything works as expected, turn test mode off and start accepting
live payments.
Fees are calculated at your live rates, so the amounts in the response match
the real ones. Test payments are flagged separately and never reach your
withdrawable balance or the statistics — you cannot request a payout for them.
The mode applies to every payment method: SBP, cards, cryptocurrency and P2P.
For P2P via h2h the response carries test payment details and a `test_url`
link to the simulation page.
# 🔗 No-code payments
You can accept payments without writing any code — with the project payment
link. The link, a QR code and ready-made snippets for a website are in the
dashboard: project → Widget tab → Payment link. No API or keys needed.
**Link.** `https://coinso.io/w/CODE` — send it to a customer, put it on a
button in a Telegram bot or on social media, or print the QR code. You can
add parameters to the link:
| Parameter | What it does |
|---|---|
| `amount` | Amount in RUB. Without it the customer enters the amount |
| `order` | Order number or customer name. Returned in the webhook as `custom` and in the return URL |
| `desc` | Payment description, up to 120 characters |
| `email` | Customer email, pre-filled in the form |
| `lang` | Form language: `ru` or `en` |
```
https://coinso.io/w/CODE?amount=1500&order=A-1042
```
**Website button.** Paste two lines into an HTML block on your site. On click
a window opens over the page where the customer enters the amount and email.
The page behind it is dimmed and blurred, and the window has no background
of its own: only the form, the logo and the language/help row are visible.
The payment takes place on the Coinso page: the customer goes there from the
window. If the amount is set in the link, there is no window and the
customer goes straight to the payment page:
```html
<script async src="https://coinso.io/widget/v1.js"></script>
<a href="https://coinso.io/w/CODE?amount=1500" class="coinso-pay coinso-pay-button">Pay</a>
```
The `coinso-pay` class turns on the window, `coinso-pay-button` is a
ready-made button look: you can drop it and style the link yourself. From
your own script the window opens like this:
`Coinso.open({ widget: 'CODE', amount: 1500, order: 'A-1042' })`.
After the payment the customer returns to your site at the return URL from
the project settings.
If your site uses a Content-Security-Policy, allow `https://coinso.io` in
`script-src` and `frame-src`.
**Form inside a page.** Use it if your site builder does not allow scripts.
The customer enters the amount and email on your page, and the payment opens
on the Coinso page:
```html
<iframe src="https://coinso.io/w/CODE?embed=1&amount=1500" width="100%" height="400"
style="border:0;max-width:420px;color-scheme:light" allow="clipboard-write"></iframe>
```
The form has a transparent background: your page shows only the form itself,
the logo and the row with the language switch and help. Keep
`color-scheme:light` in the code: without it, on sites with a dark color
scheme the browser paints the background white.
> **Important.** A customer can change the amount in the link, so check the
> amount in the payment notification before you deliver. The customer's
> return to your site is not a payment confirmation: deliver on the webhook
> or on the record in your dashboard. If the amount must be fixed, create
> the invoice with `POST /payment/create`.
An invoice created by the link is a regular invoice: the same payment
methods, limits, fees, the `payment.success` webhook and return URLs from
the project settings.
# ⚠️ Integration Errors
The API always returns JSON with a `success: false` field and a text description of the error in `message` if something goes wrong. Handle the following HTTP statuses for your system to correctly react to integration errors:
* **400 Bad Request** — Parameters error (for example, forgot to pass amount, specified a negative value, or forgot project_id).
* **401 Unauthorized** — Authorization error. API Key passed instead of Secret Key, or the key is invalid/missing.
* **403 Forbidden** — Project not found, doesn't belong to you, or is blocked (status other than active).
* **405 Method Not Allowed** — Invalid HTTP method used (only POST is allowed).
* **500 Internal Server Error** — Internal server error.
* **502 Bad Gateway** — With h2h the provider did not issue payment details (SBP or P2P). The invoice is closed; create a new one.
version: 1.0.0
servers:
- url: /api
description: Main Server
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: Use your Secret Key as a Bearer token.
security:
- bearerAuth: []
paths:
/payment/create:
post:
summary: Create Payment Invoice
description: Creates a new invoice and returns a unique link to the payment form. Make sure you pass the correct `project_id` and a valid `Secret Key` in the headers.
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- project_id
- amount
properties:
project_id:
type: integer
description: ID of your project
example: 123
amount:
type: number
description: Payment amount in Rubles (must be greater than zero)
example: 1500.50
description:
type: string
description: Payment purpose (will be shown to the client)
example: Payment of order #456
custom:
type: string
maxLength: 255
description: >-
Your own order identifier. Returned unchanged in the webhook —
use it to match a payment with an order in your system.
example: order-12345-user-777
method:
type: string
enum: [sbp, crypto, card, p2p_sbp, p2p_card]
description: >-
Payment method. Optional for standard: the payer picks the method
on our payment page, and a method you pass is validated — whether
it is enabled for the project and whether the amount fits its
limits. Required for h2h: sbp and crypto, plus p2p_sbp and
p2p_card when H2H for P2P is enabled for the project (see
"P2P methods"). Card payments are not available through h2h.
example: sbp
integration_type:
type: string
enum: [standard, h2h]
default: standard
description: >-
standard — the response contains a link to our payment page.
h2h — payment details (SBP link, crypto address or P2P details) are returned
directly, and you render the page yourself.
example: standard
currency:
type: string
description: >-
Cryptocurrency for h2h with method=crypto. Defaults to
usdt_trc20. Which codes are available to your project is
returned by GET /payment/methods — the set depends on your
settings, so request the list instead of storing it.
enum:
- usdt_trc20
- usdt_ton
- usdt_bep20
- usdt_erc20
- usdt_polygon
- usdt_arbitrum
- usdt_solana
- usdc_erc20
- usdc_bep20
- usdc_base
- usdc_arbitrum
- usdc_polygon
- usdc_solana
- ton
- btc
- trx
- ltc
- eth
example: usdt_trc20
p2p_channel:
type: string
enum: [sbp_p2p, tbank, vtbbank, ozonbank, mobile_topup, p2p_cis, card_ru]
description: >-
h2h with method=p2p_sbp or p2p_card only: a sub-method code (see
the table in "P2P methods"). The sub-method must belong to the
chosen method and accept the amount — the ones available right
now are in channels of GET /payment/methods. If omitted, the first
sub-method that accepts the amount is used. An unavailable code
returns 400 with the available_channels list.
example: tbank
client_email:
type: string
format: email
maxLength: 190
description: >-
Payer's email, the preferred contact. For h2h one of the three
is required: client_email, client_phone or client_telegram_id.
example: buyer@example.com
client_phone:
type: string
description: >-
Payer's phone, 10 to 15 digits. Brackets, spaces and dashes are
ignored; a leading 8 in a Russian number is normalised to 7.
example: '+7 900 123-45-67'
client_telegram_id:
type: string
description: >-
Payer's Telegram chat id — the numeric identifier, not @username.
Group chats start with a minus sign.
example: '547742823'
client_ip:
type: string
description: >-
Payer's IP. Required for integration_type=h2h. Ignored with
standard: there the buyer reaches our page directly and the
address is taken from the connection.
example: 95.24.11.7
client:
type: object
description: >-
The same data as a single object — an alternative to the flat
client_* fields. Use either form, no need to mix them.
properties:
email:
type: string
example: buyer@example.com
phone:
type: string
example: '+79001234567'
telegram_id:
type: string
example: '547742823'
ip:
type: string
example: 95.24.11.7
success_url:
type: string
maxLength: 255
description: >-
Where to send the buyer after a successful payment. Set per
invoice, it overrides the address from the project settings —
handy when your order page is dynamic. Omit it and the project
address is used, exactly as before.
example: "https://shop.example/thanks?order=777"
fail_url:
type: string
maxLength: 255
description: >-
Same for a failed payment. If set neither here nor in the
project, success_url is used.
example: "https://shop.example/oops?order=777"
responses:
'200':
description: Invoice created successfully
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
example: true
invoice_id:
type: string
description: >-
Invoice identifier: an 8-character string. Use it to check the
status; it also arrives in the webhook as invoice_id.
example: "LTkPVA04"
payment_url:
type: string
format: uri
description: Payment page link. Returned only for integration_type=standard.
example: "https://coinso.io/pay/LTkPVA04"
nspk_link:
type: string
description: SBP payment link. Returned only for h2h with method=sbp.
example: "https://qr.nspk.ru/AD10102US2QFTQ9P8598C1KFGAN7AO0P"
wallet:
type: string
description: Wallet address. Returned only for h2h with method=crypto.
example: "TXYZ...9fK"
method:
type: string
description: Payment method. Returned only for h2h with a P2P method.
example: p2p_sbp
channel:
type: string
description: Sub-method the payment details were issued for. Returned only for h2h with a P2P method.
example: tbank
requisite:
type: object
description: >-
Payment details for the transfer. Returned only for h2h with
method=p2p_sbp or p2p_card. Show the payer value, bank and the
amount.
properties:
type:
type: string
enum: [card, phone, phone_bank, account, link, qr, pdf]
description: >-
What value holds: card — a card number; phone_bank — a phone
number for an SBP transfer, the recipient bank is in bank;
phone and account — a phone or account number to top up;
link, qr, pdf — a link.
example: phone_bank
value:
type: string
example: "+7 900 123-45-67"
holder:
type: string
nullable: true
description: Recipient, when known.
example: Ivan I.
bank:
type: string
nullable: true
description: Recipient bank or operator.
example: T-Bank
country:
type: string
nullable: true
description: Recipient country (ru, uz, tj, kg).
example: ru
payer_warning:
type: string
nullable: true
description: >-
Payment condition to show the payer next to the details.
Returned only for h2h with a P2P method.
example: Переводить только с Т-Банка
payer_banks:
type: string
nullable: true
description: Codes of the banks allowed to pay from, when the condition is a list.
example: tbank
amount:
type: number
description: >-
Amount to pay. For crypto it is denominated in the chosen currency
and carries unique decimals — the payment is identified by them,
so it must not be changed. For P2P — the exact transfer amount in
rubles: the payer must send exactly this.
example: 25.00
expires_in:
type: integer
description: >-
How many seconds the payment details stay valid. For P2P — the
lifetime of the details, about 600 seconds.
example: 1800
status_url:
type: string
format: uri
description: Ready-made link to check this invoice status.
example: "https://coinso.io/api/payment/status?uuid=LTkPVA04"
test_mode:
type: boolean
description: >-
Present only while the project is in test mode. In that case a
simulation page link is returned instead of real payment details,
and no money is charged.
example: true
test_url:
type: string
format: uri
description: >-
Payment simulation page. Returned only for h2h with a P2P method
in test mode: the payment details in the response are test ones.
example: "https://coinso.io/pay/test?txn=1426"
'400':
description: >-
Parameter validation error: negative amount, missing project_id, an
invalid client_email / client_phone / client_telegram_id / client_ip,
a method not enabled for the project or an amount outside its limits,
H2H for P2P not enabled, or no contact / client_ip for P2P via h2h. For
an unavailable p2p_channel the response carries available_channels — the
codes that fit.
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
example: false
message:
type: string
example: "Invalid parameters"
'401':
description: Authorization error (API Key passed instead of Secret Key, or key is invalid)
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
example: false
message:
type: string
example: "Invalid Secret Key"
'403':
description: Project not found or blocked (status is not active)
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
example: false
message:
type: string
example: "Project not found or not active"
'405':
description: Invalid HTTP method used (only POST is allowed)
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
example: false
message:
type: string
example: "Method Not Allowed"
'502':
description: >-
The provider did not issue payment details (h2h for SBP or P2P). The
invoice is closed as failed: create a new one or offer the payer another
method.
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
example: false
message:
type: string
example: "Способ временно недоступен. Пожалуйста, выберите другой способ оплаты."
/payment/methods:
get:
summary: Available payment methods and currencies
description: >-
Returns what is available to the project right now: enabled methods,
amount limits and, for crypto, the full list of currencies with their
networks and icons. The set of currencies is not fixed — it depends on
configured wallets, on moderation and on which coins you kept enabled
in your dashboard — so for h2h request the list here instead of
hardcoding it on your side.
parameters:
- name: project_id
in: query
required: true
schema:
type: string
description: Your Merchant ID from the project settings.
example: "226785123"
- name: amount
in: query
required: false
schema:
type: number
description: >-
Invoice amount in RUB. When provided, methods outside their amount
limits get available = false and the reason in unavailable_reason.
example: 1500.50
responses:
'200':
description: List of methods
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
example: true
project_id:
type: string
example: "226785123"
currency:
type: string
description: Invoice currency. All amounts below are in it.
example: RUB
methods:
type: array
items:
type: object
properties:
method:
type: string
enum: [crypto, sbp, card, p2p_sbp, p2p_card]
example: crypto
name:
type: string
example: Криптовалюты
min_amount:
type: number
example: 100
max_amount:
type: number
nullable: true
description: null means there is no upper limit.
example: null
available:
type: boolean
description: >-
false when the provided amount does not fit this
method's limits.
example: true
unavailable_reason:
type: string
description: Present only when available = false.
example: "Минимальная сумма для криптовалюты: 100 ₽"
h2h_supported:
type: boolean
description: >-
Whether the method supports h2h. Card payments are
available only through our hosted payment page, P2P
only when H2H for P2P is enabled for the project.
example: true
currencies:
type: array
description: Present only for method = crypto.
items:
type: object
properties:
code:
type: string
description: Value for the currency field when creating an invoice.
example: usdt_trc20
name:
type: string
example: USDT TRC20
symbol:
type: string
example: USDT
network:
type: string
example: TRC20
icon:
type: string
example: https://coinso.io/img/usdt_trc20.svg
group:
type: string
nullable: true
description: >-
The coin, when it is issued in several networks
(usdt, usdc). null — the coin has a single network.
example: usdt
channels:
type: array
description: >-
P2P methods only: sub-methods the payment details are
issued for. Pass code as p2p_channel with h2h. The
method's own min_amount and max_amount already take the
sub-method limits into account.
items:
type: object
properties:
code:
type: string
description: Sub-method code — the value for p2p_channel.
example: tbank
name:
type: string
example: Т-Банк → Т-Банк
min_amount:
type: number
example: 2000
max_amount:
type: number
nullable: true
example: 100000
available:
type: boolean
description: false when the provided amount is outside the sub-method limits.
example: true
payer_warning:
type: string
nullable: true
description: Payment condition for the payer.
example: Переводить только с Т-Банка
'401':
description: Key missing or invalid
'403':
description: Project not found or not active
/payment/status:
get:
summary: Invoice status
description: >-
Returns the current status of an invoice. No authorization is required —
knowing the invoice_id is the access. Use this as confirmation before
releasing goods: the webhook is sent once and may not arrive.
security: []
parameters:
- name: uuid
in: query
required: true
schema:
type: string
description: The invoice_id returned when the invoice was created.
example: LTkPVA04
responses:
'200':
description: Status found
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
example: true
status:
type: string
enum: [pending, paid, expired, failed]
description: >-
pending — awaiting payment, paid — settled, expired — the window
closed, failed — the payment did not go through.
example: paid
success_url:
type: string
description: >-
Customer return address: the one passed when the invoice was
created, otherwise the one from the project settings.
example: "https://example.com/success"
invoice_id:
type: string
description: The invoice identifier — the one you asked about.
example: LTkPVA04
custom:
type: string
nullable: true
description: Your order identifier, as passed when the invoice was created.
example: order-12345-user-777
amount:
type: string
description: Invoice amount in rubles.
example: "1500.50"
description:
type: string
description: Payment purpose from the invoice.
example: Order #456
currency:
type: string
nullable: true
description: >-
Currency of the latest payment attempt: RUB for SBP, cards and
P2P, the coin code for crypto. null until the payer picks a method.
example: usdt_trc20
payment_method:
type: string
nullable: true
description: Method of the latest attempt — sbp, card, crypto, p2p_sbp or p2p_card.
example: crypto
/webhook-example:
post:
summary: Webhook payment notification (Your server)
description: >-
A POST request is sent to your Webhook URL (set in the project settings) on
successful payment and on cancellation.
**Signature.** Every request is signed: the `X-Signature` header carries an
HMAC-SHA256 of the raw request body, keyed with your project Secret Key (the
same one used in `Authorization: Bearer`). Compute it before parsing JSON,
otherwise it will not match:
`$expected = hash_hmac("sha256", file_get_contents("php://input"), $secretKey);`
Compare with `hash_equals` and reject requests whose signature does not match.
**Sender address.** Notifications arrive from 31.43.163.127. You may filter by
it, but the signature is stronger: the address can change, while the secret is
known only to you.
**No retries.** A notification is sent once, so confirm the payment with
`GET /payment/status` before releasing goods.
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
event:
type: string
enum: [payment.success, payment.failed]
example: payment.success
invoice_id:
type: string
description: The invoice this notification belongs to — the one returned at creation.
example: "LTkPVA04"
transaction_id:
type: integer
description: >-
Identifier of a payment attempt within the invoice. A different
entity from invoice_id: one invoice may have several attempts.
example: 98765
custom:
type: string
description: Your order identifier, if you passed one when creating the invoice.
example: order-12345-user-777
description:
type: string
description: Payment purpose from the invoice.
example: Payment of order #456
amount:
type: number
example: 1500.50
fee_amount:
type: string
description: Fee withheld.
example: "0.00"
credited_amount:
type: string
description: >-
Amount credited to your balance — the invoice amount minus the
fee.
example: "1000.00"
currency:
type: string
example: RUB
payment_method:
type: string
description: sbp, card, crypto, p2p_sbp or p2p_card.
example: sbp
status:
type: string
example: completed
responses:
'200':
description: Your server must return a 200 OK status to confirm successful receipt of the webhook.