Hosted payment form

Take a card payment on the payment form: you create a pay session, the payer enters the card on the form. Card data never passes through your servers, so your servers stay out of PCI DSS scope.

When the payer submits the form, a card payin with the session's merchant_reference is created. From then on it is a regular card payin: the same statuses, hold, refunds and webhooks. One payin per session.

Use the key of a payin shop; a payout shop key gets 403 method_not_enabled. Rules shared by every method — Authentication, Idempotency, Errors, Webhooks — apply here and are not repeated.

Flow

  1. Generate merchant_reference and save it with your order before the call.
  2. Create the session: POST /v2/pay-sessions. Send the payer to url, or embed it in an iframe on a domain allowed for the shop.
  3. The payer enters the card and submits. A card payin is created; the payment page runs 3DS when the issuer asks for it, then sends the payer to return_urls.
  4. Wait for the webhook payin.completed or payin.failed, or payin.authorized if the shop holds card payins. Verify the signature and act on data.object — the full payin.
  5. Fallback: if no webhook has come for a minute, you can start polling: find the payin with GET /v2/transactions?merchant_reference=… while you do not know its id, then read it with GET /v2/transactions/{transaction_id} until it is final. Polling instead of webhooks loads the API and gets 429 rate_limited (R20).

Endpoints

Step Endpoint Operation
Create a session POST /v2/pay-sessions createPaySession
Capture, void, refund As for a card payin capturePayin, voidPayin, createRefund
Read the payin GET /v2/transactions?merchant_reference=… or GET /v2/transactions/{transaction_id} — common for all transactions, see Transactions listTransactions, retrieveTransaction

The create has its own section below. It returns the Pay session object; the payin is the card Payin object.

POST /v2/pay-sessions

Creates a pay session and returns the link to the form.

Request

Headers: Authorization: Bearer <payin 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; the payin gets the same value
amount object yes
amount.value string yes Major units, at most two decimals, at least 0.01: "75.00". A string, never a number
amount.currency string yes ISO 4217, the shop currency: EUR
description string yes Up to 255 characters. Shown to the payer on the form
expires_at date-time yes When the form stops accepting the payment. In the future, with a time zone offset: 2026-09-29T10:40:00Z
payer object yes The person who pays
payer.email string yes Email address
payer.first_name string no If first_name or last_name is omitted, both are taken from the cardholder name the payer types on the form
payer.last_name string no
payer.ip_address string no IPv4 or IPv6. If omitted, the IP the payer submits the form from
payer.address object yes Billing address
payer.address.country string yes ISO 3166-1 alpha-2: US
payer.address.state string for US, CA, AU payers ISO 3166-2 subdivision code without the country prefix, 2–3 letters: CA
payer.address.city string yes Not digits only
payer.address.postal_code string yes
payer.address.line1 string yes Street and house number
payer.address.line2 string no Apartment, suite, floor
card_token string no card.token of an earlier completed payin of this shop. The form shows the saved card instead of the card fields
save_card boolean no true — the payin returns card.token when it is completed. Works if the shop allows saving cards
return_urls object no
return_urls.success string no https:// URL. The payment page sends the payer there after completed
return_urls.failure string no https:// URL. The payment page sends the payer there after failed
curl -X POST https://payments-api.mrcr.io/v2/pay-sessions \
  -H "Authorization: Bearer $PAYIN_SHOP_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "merchant_reference": "order-50001",
    "amount": { "value": "75.00", "currency": "EUR" },
    "description": "Order 50001",
    "expires_at": "2026-09-29T10:40:00Z",
    "payer": {
      "email": "jane@example.com",
      "address": { "country": "US", "state": "CA", "city": "San Francisco", "postal_code": "94105", "line1": "1 Market Street" }
    },
    "return_urls": { "success": "https://shop.example.com/pay/success", "failure": "https://shop.example.com/pay/failure" }
  }'

Response 201 Created

The Pay session object in data. A repeat with the same merchant_reference and body returns the same session and the same url, also after expires_at.

Header Meaning
Idempotent-Replayed: true The same merchant_reference and body were sent before. This is the existing session; nothing was created again
{
  "data": {
    "id": "9c1e7a2f4b8d3c6e0a5f1b9d7e3c2a8f4b6d0e1c9a7f3b5d2e8c4a6f0b1d3e5c",
    "object": "pay_session",
    "merchant_reference": "order-50001",
    "amount": { "value": "75.00", "currency": "EUR" },
    "url": "https://pay-in.mrcr.io/pay/9c1e7a2f4b8d3c6e0a5f1b9d7e3c2a8f4b6d0e1c9a7f3b5d2e8c4a6f0b1d3e5c",
    "expires_at": "2026-09-29T10:40:00Z",
    "payin_id": null,
    "created_at": "2026-09-29T09:40:00Z"
  }
}

Errors

No session is created on any of them.

HTTP code When Retry
400 validation_failed A field is missing or malformed — details[] names it. Also: expires_at is not in the future; an unknown card_token; 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 The payment form is not set up for the shop, or the key is of a payout shop No. Use the payin shop key, or ask Mercuryo to set it up
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 session
429 rate_limited Too many requests Yes, after Retry-After seconds
500 internal_error Failure on our side Yes, the identical request

After a timeout or a dropped connection the session may or may not exist. Resend the identical request (R4), never with a new reference.

Opening the form

  • Full page or a new window. Send the payer to url.
  • Iframe. Put url in an iframe on a domain allowed for the shop. The list of domains is set for the shop by Mercuryo; on any other domain the browser refuses to show the form.
  • The look of the form — theme, terms and conditions — is set for the shop.
  • After the payer submits, the form goes on to the payment page: it runs 3DS when the issuer asks for it and then sends the payer to return_urls. Coming back is not a result — act on the webhook.

The Pay session object

Returned by the create.

Field Type Always set Meaning
id string yes Session id; the last part of url
object string yes pay_session
merchant_reference string yes As you sent it; the payin gets the same value
amount object yes As you sent it
amount.value string yes Major units: "75.00"
amount.currency string yes ISO 4217: EUR
url string yes The payment form
expires_at date-time yes UTC. When the form stops accepting the payment
payin_id string or null yes The payin created when the payer submitted the form; null until then
created_at date-time yes UTC

After the form

The result of the payment is the status of the payin — see card payin statuses.

payin_id expires_at What it means What you do
null in the future The payer has not submitted the form yet Wait
null passed The session expired unpaid Create a new session with a new merchant_reference
set — The payer submitted the form Follow the payin by its webhooks

A declined payer cannot try another card on the same session: a new attempt is a new session.

Webhooks

The session sends no webhooks of its own. The payin created from it sends the card payin webhooks: payin.authorized, payin.capture_failed, payin.void_failed, payin.completed, payin.failed, and refund.* for its refunds. Match them to your order by merchant_reference.

Headers, signature check and redelivery — Webhooks.

In the sandbox

The form takes the card test data. The cents of amount.value select the outcome of the payin, as for a card payin.

Next: Card payout