Skip to content

Host-to-host

Host-to-host (H2H): your page or app shows the payer where to transfer, and the payer never leaves you. You take on what the hosted page does — showing the account, the countdown, the outcome.

When you know how the payer pays, say so in payment_method. The account is then given without asking the payer anything:

examples/curl/payments/create-host-to-host.sh
#!/usr/bin/env bash
# Create a payment whose method and payer's bank you know: the account comes without the payment page.
set -euo pipefail
. "$(dirname "$0")/../signed-request.sh"
body=$(cat <<JSON
{
"merchant_order_id": "${ORDER_ID:-order-$(mka_new_key)}",
"amount": "${AMOUNT:-2500.00}",
"currency": "LKR",
"payment_method": {
"type": "bank_transfer",
"payer_institution": "${PAYER_BANK:-boc}"
}
}
JSON
)
mka_request POST "/v1/payments" "$body" "$(mka_new_key)"
  • type — the method. Today: bank_transfer, a transfer to an account. Methods are data: new ones may appear without a new API version.
  • payer_institution — optional, the payer’s bank: boc (Bank of Ceylon), peoples (People’s Bank), combank (Commercial Bank of Ceylon), hnb (Hatton National Bank), ndb (NDB Bank), sampath (Sampath Bank), or other. It is a preference: an account in that bank while one has room, any other after it — then the transfer goes between banks through CEFTS.

A method or a bank the catalog does not list is 422 method_unavailable.

Read the payment until it is requires_action with customer_action.kind: bank_transfer — usually a few seconds after creation — and show exactly what it says:

"customer_action": {
"kind": "bank_transfer",
"bank_transfer": {
"bank_name": "Bank of Ceylon",
"account_name": "K. A. Perera",
"account_number": "0081234567"
}
},
"expires_at": "2026-10-22T18:49:01Z"
examples/curl/payments/get.sh
#!/usr/bin/env bash
# Read a payment: the truth after a webhook, a timeout or a 503.
set -euo pipefail
. "$(dirname "$0")/../signed-request.sh"
payment_id=$(mka_required PAYMENT_ID)
mka_request GET "/v1/payments/$payment_id"
  • Show the exact amount: a different amount is not matched to the payment.
  • Show payment_reference when it is present; the payer puts it in the transfer’s description.
  • Count down to expires_at. After it, hide the account: a transfer then is credited only by support.
  • Never cache the account. Each GET answers it fresh and stops answering it once it may no longer be used.

The same statuses and webhooks as any payment (lifecycle). On succeeded, tell the payer; on failed with expired, offer to try again with a new payment.

Without payment_method, a project that lets payers choose answers requires_action with customer_action.kind: method_selection: the methods and banks on offer, and selection_expires_at. The choice is made on checkout_url — send the payer there. Unchosen within 30 minutes, the payment fails expired. A kind you do not know: also send the payer to checkout_url.