Card payout
Send money from the shop balance to a bank card.
Use the key of a payout shop; a payin shop key gets 403 method_not_enabled. Rules shared by every
method — Authentication, Idempotency,
Errors, Webhooks — apply here and are
not repeated.
Flow
- Generate
merchant_referenceand save it with your payout record before the call. - Create the payout:
POST /v2/payouts/card. Saveidfrom the response. -
Wait for the webhook
payout.completedorpayout.failed. Verify the signature and act ondata.object— the full payout. -
Fallback: if no webhook has come for a minute, you can start polling the status with
GET /v2/transactions/{transaction_id}until it is final. Polling instead of webhooks loads the API and gets429 rate_limited(R20).
Endpoints
| Step | Endpoint | Operation |
|---|---|---|
| Create | POST /v2/payouts/card |
createCardPayout |
| Read one | GET /v2/transactions/{transaction_id} — common for all transactions, see Read one transaction |
retrieveTransaction |
Separate from a payout: the shop balance, GET /v2/balances (listBalances) — see Balance.
The create has its own section below: request, response, example, errors. Reading returns the Payout object.
POST /v2/payouts/card
Creates a payout and blocks its amount on the balance.
Request
Headers: Authorization: Bearer <payout shop key>, Content-Type: application/json.
| Field | Type | Required | Format and limits |
|---|---|---|---|
merchant_reference |
string | yes | 1–100 characters. Unique per merchant across payins, refunds, payouts and pay sessions. The idempotency key of this call |
amount |
object | yes | |
amount.value |
string | yes | Major units, at most two decimals, at least 0.01: "300.00". A string, never a number |
amount.currency |
string | yes | ISO 4217, the shop currency: EUR |
card |
object | yes | |
card.number |
string | yes | 12–19 digits, no spaces, passes the Luhn check |
card.cardholder_name |
string | yes | Latin letters and spaces, as printed on the card |
recipient |
object | no | The person who receives the money |
recipient.phone |
string | no | +14155550123 |
recipient.first_name |
string | no | First and middle names. If first_name or last_name is omitted, both are taken from card.cardholder_name |
recipient.last_name |
string | no | |
recipient.email |
string | no | Email address |
recipient.birth_date |
string | no | YYYY-MM-DD |
recipient.nationality |
string | no | ISO 3166-1 alpha-2: US |
recipient.address |
object | no | |
recipient.address.country |
string | no | ISO 3166-1 alpha-2: US |
recipient.address.state |
string | for US, CA, AU recipients | ISO 3166-2 subdivision code without the country prefix, 2–3 letters: CA |
recipient.address.city |
string | no | Not digits only |
recipient.address.postal_code |
string | no | |
recipient.address.line1 |
string | no | Street and house number |
recipient.address.line2 |
string | no | Apartment, suite, floor |
sender |
object | no | The person on whose behalf you send the payout |
sender.birth_date |
string | no | YYYY-MM-DD |
sender.ip_address |
string | no | IPv4 or IPv6 |
purpose |
object | no | What the payout is for |
purpose.details_url |
string | no | A page with the payment details on your side, http:// or https://, up to 255 characters |
purpose.digital_asset_type |
string | no | The digital asset the payout is for: cbdc, stablecoin, native_token, nft, other — see Digital asset type |
sub_merchant |
object | no | Your client the payout is made for, when you pay out on behalf of other businesses |
sub_merchant.id |
string | no | Your id for the client, letters and digits, up to 15 characters. If omitted, your merchant account id is used |
sub_merchant.country |
string | no | ISO 3166-1 alpha-2: PT. If omitted, your shop country is used |
curl -X POST https://payments-api.mrcr.io/v2/payouts/card \
-H "Authorization: Bearer $PAYOUT_SHOP_KEY" \
-H "Content-Type: application/json" \
-d '{
"merchant_reference": "payout-10001",
"amount": { "value": "300.00", "currency": "EUR" },
"card": { "number": "4111111111111111", "cardholder_name": "JOHN SMITH" },
"recipient": {
"first_name": "John", "last_name": "Smith", "phone": "+14155550123",
"address": { "country": "US", "state": "CA", "city": "San Francisco", "postal_code": "94105", "line1": "1 Market Street" }
}
}'
Response 201 Created
The Payout object in data, in created. Any status is possible,
including a final one when the call repeats an earlier one.
| Header | Meaning |
|---|---|
Idempotent-Replayed: true |
The same merchant_reference and body were sent before. This is the existing payout in its current status; nothing was created again |
{
"data": {
"id": "6b2f0e8a-3c7d-4e4b-9a9f-1d8c4e7b0ad6",
"object": "payout",
"merchant_reference": "payout-10001",
"method_type": "payout_card",
"status": "created",
"status_details": null,
"amount": { "value": "300.00", "currency": "EUR" },
"card": { "brand": "visa", "last4": "1111" },
"created_at": "2026-09-29T12:00:00Z",
"updated_at": "2026-09-29T12:00:00Z"
}
}
Errors
No payout is created on any of them.
| HTTP | code |
When | Retry |
|---|---|---|---|
| 400 | validation_failed |
A field is missing or malformed — details[] names it. Also: recipient.address.state missing for US, CA, AU; the amount has more than two decimals or is not in the shop currency |
No. Fix the request |
| 401 | authentication_failed |
The key is missing, malformed, revoked, or sent to the wrong host | No |
| 403 | method_not_enabled |
Card payouts are not enabled for the shop, or the key is of a payin shop | No. Use the payout shop key, or ask Mercuryo to enable the method |
| 409 | merchant_reference_conflict |
The reference was used with a different body | No. Resend the original body, or use a new reference for a new payout |
| 429 | rate_limited |
Too many requests | Yes, after Retry-After seconds |
| 500 | internal_error |
Failure on our side | Yes, the identical request |
{
"error": {
"code": "validation_failed",
"message": "amount.currency must be the shop currency",
"details": [{ "field": "amount.currency", "message": "must be EUR" }]
}
}
After a timeout or a dropped connection the payout may or may not exist. Resend the identical request (R4), never with a new reference.
The Payout object
Returned by the create and by GET /v2/transactions/{transaction_id}, and sent in webhooks as
data.object. It has the common fields of
every transaction, with these values and fields for a card payout:
| Field | Type | Always set | Meaning |
|---|---|---|---|
id |
string | yes | UUID. Use it in GET /v2/transactions/{transaction_id} |
object |
string | yes | payout |
method_type |
string | yes | payout_card |
status |
string | yes | created, processing, completed, failed — see Statuses |
status_details.code |
string | when failed |
See Decline reasons |
rrn |
string | no | Retrieval reference number of the payout in the card network, when the provider returns it. Give it to the recipient if their bank cannot find the payment |
card |
object | no | The card the payout went to |
card.brand |
string | no | Card scheme: visa, mastercard |
card.last4 |
string | no | Last four digits |
Digital asset type
Card networks classify payouts made for digital assets. Send purpose.digital_asset_type when the
payout is for one.
| Value | The payout is for |
|---|---|
cbdc |
A central bank digital currency or a tokenized deposit |
stablecoin |
A fiat-backed stablecoin |
native_token |
A blockchain native token or coin |
nft |
A non-fungible token |
other |
Another or an unknown digital asset |
If the value is omitted or the provider of the payout does not take it, the provider's default for the
card brand is used, other if there is none.
Statuses
| Status | What it means | The amount on your balance | Webhook | What you do | Next |
|---|---|---|---|---|---|
created |
Accepted and recorded. Processing has not started | Blocked | — | Wait | processing, failed |
processing |
Checks run; the payout is at the provider or the recipient's bank | Blocked | — | Wait. Do not create another payout for the same purpose | completed, failed |
completed |
Final. Credited to the recipient's card | Written off | payout.completed |
Mark it paid on your side | — |
failed |
Final. Not credited. status_details.code says why |
Returned to the balance | payout.failed |
Act on the decline reason | — |
- Only
completedandfailedare outcomes. A final status never changes. -
Webhooks come for the final statuses:
payout.completedandpayout.failed. Wait for them; read the status only as the fallback (R20). -
How long
processinglasts depends on the recipient's bank. Do not resolve a payout by a timeout on your side: keep waiting for the webhook (R8). - A payout cannot be canceled once accepted.
- An unknown status is not final — keep waiting (R13).
Decline reasons
status_details.code on a failed payout. Every decline is final: a new attempt is a new payout with
a new merchant_reference. The blocked amount is already back on the balance.
| Code | When | What you do |
|---|---|---|
card_not_supported |
Card type, country or issuer is not accepted | Ask the recipient for another card |
invalid_recipient_details |
The recipient's bank rejected the card number or the recipient data | Check the card number and recipient data with the recipient, then create a new payout |
method_unavailable |
No route for this card right now | Try again later with a new payout |
insufficient_funds |
The shop balance did not cover the payout | Top up the balance, then create a new payout |
limit_exceeded |
A per-transaction, daily or monthly limit | Wait for the limit to reset, or send a smaller amount |
risk_rejected |
Rejected by antifraud | Do not retry automatically. Contact support |
provider_declined |
The provider declined | Try again later. If declines repeat, ask for another card |
internal_error |
Failure on our side; nothing was credited | Try again with a new payout. Quote id to support if it repeats |
An unknown code: handle it like provider_declined.
Webhooks
type |
When | data.object |
|---|---|---|
payout.completed |
The payout reaches completed |
The Payout object |
payout.failed |
The payout reaches failed |
The Payout object, with status_details |
{
"id": "9e5c3b1d-6f0a-4b7e-8d2c-4a1f7b0e3d09",
"type": "payout.failed",
"shop_id": 1025,
"created_at": "2026-09-29T12:00:09Z",
"data": {
"object": {
"id": "6b2f0e8a-3c7d-4e4b-9a9f-1d8c4e7b0ad6",
"object": "payout",
"status": "failed",
"status_details": { "code": "invalid_recipient_details" },
"…": "…"
}
}
}
Headers, signature check and redelivery — Webhooks.
Balance
The shop balance payouts are paid from.
| Moment | What happens to the amount |
|---|---|
POST /v2/payouts/card accepted |
Blocked: subtracted from available |
| The balance does not cover the amount | The payout ends failed with insufficient_funds; nothing is blocked |
The payout is completed |
Written off |
The payout is failed |
Returned to available |
GET /v2/balances
The balance payouts are paid from. Call it when you need the balance — for example, to show it or to plan payouts. A payout does not need it: the create checks the balance itself.
Request
Headers: Authorization: Bearer <payout shop key>.
curl https://payments-api.mrcr.io/v2/balances \
-H "Authorization: Bearer $PAYOUT_SHOP_KEY"
Response 200 OK
| Field | Type | Always set | Meaning |
|---|---|---|---|
data[] |
array | yes | One item: a shop has one currency |
data[].currency |
string | yes | ISO 4217, the shop currency: EUR |
data[].available |
object | yes | What you can pay out now. Amounts of payouts that are not final yet are already subtracted |
data[].available.value |
string | yes | Major units, at most two decimals: "12500.00". A string, never a number |
data[].available.currency |
string | yes | ISO 4217, the shop currency: EUR |
{
"data": [
{ "currency": "EUR", "available": { "value": "12500.00", "currency": "EUR" } }
]
}
Errors
401 authentication_failed, 429 rate_limited, 500 internal_error —
see Errors.
In the sandbox
The cents of amount.value select the outcome.
| Cents | Example | Outcome |
|---|---|---|
| any other | 300.00 |
completed |
.51 |
300.51 |
failed — provider_declined |
.52 |
300.52 |
failed — risk_rejected |
.53 |
300.53 |
failed — limit_exceeded |
.54 |
300.54 |
failed — card_not_supported |
.55 |
300.55 |
failed — method_unavailable |
.56 |
300.56 |
failed — internal_error |
.60 |
300.60 |
failed — invalid_recipient_details |
.61 |
300.61 |
processing for 60 seconds, then completed |
Sandbox keys and fixtures — Sandbox.
Next: Authentication