Getting started

Responses & errors

The response envelope, field formats, error codes and keys, and how repeated requests are handled.

Response envelope

Every response has the same shape:

JSON
{
  "code": 0,
  "msg": "success",
  "data": {}
}
  • code is 0 on success. Any other value is an error, and data is left out.
  • msg is success, or the error message with its key (see Errors).

The HTTP status is 200 for every signed response, including errors. Always read code, which is a field in the JSON body and not an HTTP status, even when its value looks like one (400, 422). The only responses with a different HTTP status are the errors HansaPay returns before it has identified your key: authentication failures (401, 403) and an unreadable request body (500). Those are not signed; see Authentication errors.

Formats

  • Amounts are objects with a currency and a string value: {"currency": "USD", "value": "12.50"}. Send value as a string with at most two decimal places. "12.5" and "12.50" are accepted; 12.50 (a JSON number) and "12.500" are refused. Responses always show two decimals.
  • Currencies are three-letter upper-case codes, such as USD or EUR.
  • Times are RFC 3339 strings with an offset, for example 2026-09-26T18:31:02+08:00. Always read the offset; do not assume UTC.
  • Text is UTF-8.

Errors

Format

An error response has a non-zero code and a msg made of a message and a stable key, separated by |:

JSON
{"code":2002,"msg":"Order not found|orderNotFound"}
  • Handle errors by code and the key, the part after the last |. The keys are stable. The message text may be reworded.
  • Some messages include details, such as the name of a field.
  • If you send Accept-Language: en, msg contains a plain English sentence without the key, for example Order not found. Detail such as field names is then left out. Use this only for messages you display, not for handling errors.

Only known errors are passed on with their own message. Any other failure is reported as System busy, please try again later|systemBusy. It is usually safe to retry that, with a new order number if the original request may have created an order; query first.

Authentication errors

These are returned before HansaPay has identified your key. They use the HTTP status shown, are not signed, and always include the key. All other errors are signed and returned with HTTP 200; in the tables below, code is the value in the JSON body.

HTTPcodemsgMeaning
401401Missing authentication headers|unauthorizedOne of the four signing headers is missing or empty
401401Merchant ID not found or invalid|merchantNotFoundThe merchant ID is not recognised
403403Invalid signature|forbiddenThe signature does not match. See Signature troubleshooting.
500500Internal server error|internalServerErrorThe request body could not be read
{
  "merchant_order_no": "ORDER-1001-069I"
}
403 · response
{
  "code": 403,
  "msg": "Invalid signature|forbidden"
}

Request errors (all endpoints)

codeKeyExample messageMeaning
400invalidRequestInvalid request: ...A field is missing or has the wrong type or format. The text names the field by the server's internal name: TradeInfo.GoodsName is trade_info.goods_name.
400invalidRequestmissing or invalid payment_method: ...payment_method is missing
3003unsupportedPaymentMethodunsupported payment_method: VENMOUnknown payment method
400mutuallyRequiredFieldsMissingmerchant_order_no and order_no cannot both be emptySend one of the two
400mutuallyExclusiveFieldsConflictmerchant_order_no and order_no cannot both be providedSend only one of the two
{
  "payment_method": "CARD",
  "merchant_order_no": "ORDER-1010-069I",
  "trans_amount": {
    "currency": "USD",
    "value": "10.00"
  },
  "notify_url": "https://merchant.example.com/hansapay/webhook",
  "return_url": "https://shop.example.com/orders/complete",
  "metadata": "customer-8841"
}
200 · response
{
  "code": 400,
  "msg": "Invalid request: Key: 'CheckoutCardRequest.TradeInfo.GoodsName' Error:Field validation for 'GoodsName' failed on the 'required' tag|invalidRequest"
}

Payment errors

codeKeyMessageMeaning
1001merchantFrozenmerchant account is disabledYour account is suspended. See What to expect.
2006amountOutOfRangeAmount is out of the allowed rangeNo available channel accepts this amount, or it cannot be rounded to a price point
3003paymentMethodUnavailablepayment_method PAYPAL is not availableThe method is not available, or not enabled for you
422duplicateMerchantOrderMerchant order number already exists; do not submit duplicatesSee Idempotency
422invalidAmountOrder amount must be a positive number
422validationFailedAmount supports at most 2 decimal placesAlso returned for "10.100"
422notSupportedcurrency is not supported currently: EURCurrency conversion is not available
422notSupportedThis payment method does not support this amount (it only accepts fixed amount tiers)Apple Pay or Google Pay amount that cannot be rounded to a price point
422paymentFailedPayment request failedThe provider declined the payment. A FAILED webhook follows.
422validationFailedpayment declinedDeclined by HansaPay's risk checks
422validationFailedNo payment channel available
422systemBusySystem busy, order creation failed
422systemBusySystem busy, please try again laterAny other failure, including an invalid card number, CVC or expiry date
2002orderNotFoundOrder not foundQuery: no such order
403orderAccessDeniedYou are not allowed to query this orderQuery: the order belongs to another merchant
500queryFailedQuery failedQuery: try again

Refund errors

All refund-creation errors use code 422; refund-query errors use code 2002.

KeyMessage
invalidRefundAmountRefund amount must be a positive number
validationFailedRefund amount supports at most 2 decimal places
orderNotFoundOriginal order not found
refundOrderNotPaidThe original order was not paid successfully and cannot be refunded
validationFailedThe original order has been charged back and cannot be refunded
refundAmountExceededRefund amount exceeds the original order amount
duplicateRefundOrderRefund order number already exists; do not submit duplicates
validationFailedThe merchant balance does not cover this refund and its fee
notSupportedThis payment channel does not support refunds
notSupportedThis payment channel does not support partial refunds; only full refunds are allowed
refundFailedRefund request failed
systemBusySystem busy, order creation failed
systemBusySystem busy, please try again later
refundNotFoundRefund order not found (query)

Payout errors

codeKeyMessage
5007unsupportedPayoutMethodpayment_method CASH_APP is not available for payouts (every valid request today)
5007unsupportedPaymentMethodunsupported payment_method: VENMO
1001merchantFrozenmerchant account is disabled
5008amountOutOfRangepayment_method CASH_APP only supports USD payouts, got EUR
5008notSupportedcurrency BRL is not supported for payout currently
5004insufficientFundsinsufficient merchant payout balance
5002notFoundpayout not found (query)
422notFoundpayout not found (cancel)
5003payoutNotCancelablepayout not cancelable in current status: SUCCESS
422notSupportedpayout channel does not support cancel

Idempotency

OperationKeyScopeRepeat request
Create a paymentmerchant_order_noYour account, shared with your refund numbersRefused: Merchant order number already exists; do not submit duplicates|duplicateMerchantOrder (code 422)
Create a refundmerchant_refund_noYour refundsRefused: Refund order number already exists; do not submit duplicates|duplicateRefundOrder (code 422)
Create a payoutmerchant_payout_noYour payoutsRefused
Webhookevent, order_no, statusSee Duplicates and order

Things to plan for:

  • A repeated request is refused, not replayed. HansaPay does not return the original response for a repeated number. If a create request times out on your side, query by your number to find out what happened before trying again with a new number.
  • A failed request still uses up the number. When a payment or refund is refused after HansaPay has recorded it (for example a declined card or a refused refund), the number cannot be used again.
  • Use different numbers for payments and refunds. A refund number that matches one of your payment order numbers can block that payment number from being used later.
  • Send each create request once. Do not send the same create request in parallel; wait for the answer, or query.