KulPayDOCS Live

Reference

Errors and codes

Two kinds of codes, and they answer different questions. A request error says why the gateway refused what you sent: it comes back at once, with an HTTP status. An outcome code says why an operation it accepted did not succeed: a declined payment, a biller that refused a bill. It arrives later, in the resource's failure_code and in its webhook, while the request itself got a 2xx. 134 request codes and 51 outcome codes are listed below.

How errors look

Every request error is an RFC 9457 problem document (application/problem+json) with a stable, machine-readable code. Branch on the code, never on the detail: details are for people and may change. Two exceptions keep the format their standard sets: POST /oauth/token answers in OAuth 2.0's (RFC 6749), and the signature check answers {"error": code}.

An error · application/problem+json

{
  "type": "about:blank",
  "title": "Unprocessable Entity",
  "status": 422,
  "code": "limit_exceeded",
  "detail": "The amount is over this PSP's per-transaction limit.",
  "rule": "max_amount"
}

Until the old envelope's sunset

{
  "type": "about:blank",
  "title": "Unprocessable Entity",
  "status": 422,
  "code": "insufficient_balance",
  "detail": "the balance (10.00 MZN) does not cover 255.00 MZN",
  "error": {                            // the old envelope, until its sunset
    "code": "insufficient_balance",
    "message": "the balance (10.00 MZN) does not cover 255.00 MZN"
  }
}

A validation problem lists each field that failed in errors[], with its own field code. Every response carries X-Request-ID: quote it to support.

Handling them

RetryWhat it means
Fix the requestThe same request fails again. Change it (the body, the key's scope, the amount) or ask the person for something else.
Retry as isTransient. Retry the same request with the same Idempotency-Key, with exponential backoff: it can never act twice.
Retry laterWait the number of seconds in Retry-After (or back off), then retry.
After an actionRetry once something else has happened: the payer's consent, a second person's approval, an endpoint enabled, KYB approved.

A 5xx without a specific code is ours. Money-moving calls are safe to retry with the same Idempotency-Key; if a payment's state is unclear, read it (GET /v1/payments/{id}) or wait for its webhook rather than charging again.

Authentication and access

API keys, scopes, signatures, client certificates, CSRF.

CodeStatusMeaningWhat to doRetry
already_rolled
Key already rolled
problem
409 POST /v1/api-keys/{id}/roll was called on a key that has already been rolled or revoked, possibly by a concurrent request. List the account's keys and roll the current one instead. Fix the request
client_certificate_required
Client certificate required
problem
401, 409 401: the account requires mTLS and the request did not present a valid client certificate issued to it. 409: PUT /v1/mtls {required: true} was called without presenting such a certificate, which would lock the caller out. Call with a valid client certificate issued to the account (issue one via POST /v1/client-certificates). Fix the request
csrf_invalid
CSRF token invalid
problem
403 A state-changing form post on the hosted checkout, its test toolbar or (when CSRF is enabled) the operator console carried no CSRF token, a token that does not match the session cookie, or a token this gateway did not issue. Reload the page to get a fresh token and submit the form again. Fix the request
csrf_missing
CSRF cookie missing
problem
403 A state-changing form post on the hosted checkout, its test toolbar or the operator console arrived without the CSRF cookie that the page render sets. Reload the page so the cookie is set, then submit again. Fix the request
customer_not_allowed
Customer not allowed for this key
problem
403 The key is restricted to a subset of customers (organisation or user ids) and the charge being created, or the payment being acted on, belongs to a customer outside that subset. Use a key whose customer restriction includes this customer. Fix the request
insufficient_scope
Insufficient scope
problem
403 The API key lacks the scope the endpoint requires (the body names it in missing_scope), or the request named another account_id without the accounts:write scope. Use a key that has the named scope, or omit account_id to act on your own account. After an action
invalid_api_key
Invalid API key
problem
401 The bearer token has the KulPay key prefix but is malformed or its checksum does not verify (mistyped, truncated or invented); it was refused before any lookup. Copy the full key again from where it was issued. Fix the request
invalid_client
OAuth client not recognised
OAuth (RFC 6749)
401 POST /oauth/token: no client authentication, a wrong client secret, a client that is revoked or whose account is suspended, or a private-key JWT assertion that does not verify (wrong audience, expiry more than five minutes away, no jti, or a jti already used). Answered with WWW-Authenticate, in the OAuth 2.0 format {"error", "error_description"} (RFC 6749 §5.2). Check the client id and secret, or the assertion: signed with the registered key, aud set to the token endpoint, a fresh jti, exp within five minutes. Fix the request
invalid_scope
OAuth scope not allowed
OAuth (RFC 6749)
400 POST /oauth/token asked for a scope the client was not granted. Ask only for scopes the client holds; an operator grants more. Fix the request
invalid_signature
Invalid signature
problem
401 A GitHub secret-scanning report sent to POST /secret-scanning/github has a signature that does not verify against GitHub's public key. Nothing for integrators; the report is ignored. GitHub should resend a correctly signed report. Fix the request
ip_not_allowed
IP not allowed
problem
403 The request came from an IP address outside the API key's IP allowlist, or outside the management-surface allowlist (mgmt.ip_allowlist), or the client IP could not be parsed. Call from an allowed address, or have the allowlist updated (note localhost may be ::1). After an action
key_expired
Key expired
problem
401 The API key is valid but past its expires_at. Issue or rotate to a new key and use it. After an action
signature_invalid
Webhook signature does not verify
{"error": code}
401 POST /v1/admin/webhooks/verify: the KulPay-Signature header is malformed (no t= or v1=), or no configured secret produces its v1 over this body. The body is {"error": "signature_invalid", "message", "t"}. Verify over the raw body exactly as received (before any JSON parsing), with the endpoint's current secret; during a secret roll, try both. Fix the request
signature_timestamp_outside_tolerance
Webhook signature too old
{"error": code}
401 POST /v1/admin/webhooks/verify: the signature's t= is further from the gateway's clock than the tolerance allows. Either the clock is wrong or the delivery is being replayed. Check the receiving server's clock (NTP); never accept an old delivery as new. The t field in the body is the timestamp that was signed. Fix the request
temporarily_unavailable
Token service unavailable
OAuth (RFC 6749)
503 POST /oauth/token on a gateway running without its database, which cannot issue tokens. Retry later; an operator must restore the gateway's store. Retry later
unauthorized
Unauthorized
problem
401 The request has no Authorization: Bearer header, or the bearer token is not a known API key or token. A WWW-Authenticate header is set when the header is missing. Send a valid API key as Authorization: Bearer <key>. Fix the request
unsupported_grant_type
OAuth grant type not supported
OAuth (RFC 6749)
400 POST /oauth/token with a grant_type other than client_credentials. Use grant_type=client_credentials. Fix the request
wrong_environment
Wrong environment
problem
401 A test key was used against live data or a live key against test data: the key's prefix does not match its environment, a ?mode= query asks for the other mode, or the payment being changed was created in the other mode. Use the key of the environment (test or live) that owns the data, and drop a conflicting ?mode=. Fix the request

Malformed requests

The body, the parameters, the content type or the size.

CodeStatusMeaningWhat to doRetry
invalid_amount
Invalid amount
problem
400 POST /v1/payments was sent with an amount that is zero or negative. Send a positive amount. Fix the request
invalid_callback_url
Invalid callback URL
problem
400 POST /v1/payments was sent with a callback_url that does not start with http:// or https://. Send an absolute HTTP(S) URL. Fix the request
invalid_currency
Invalid currency
problem
400 POST /v1/payments was sent with a currency that is not 3 upper-case letters. Send an ISO 4217 code such as MZN. Fix the request
invalid_request
Invalid request
problem
400 The request is malformed or refused on its merits: unparseable body, a missing or out-of-range field or query parameter, or a rule the service checks (bills, billers, partners, events, statements, settlements, audit, keys and more). The message names the problem. Fix the request as the message or detail says. Fix the request
invalid_return_url
Invalid return URL
problem
400 POST /v1/payments was sent with a return_url that neither starts with http nor with /. Send an absolute URL or a path starting with /. Fix the request
metadata_too_large
Too many metadata keys
problem
400 POST /v1/payments was sent with more than 50 metadata keys. Send at most 50 metadata keys. Fix the request
metadata_value_too_large
Metadata key or value too long
problem
400 POST /v1/payments was sent with a metadata key longer than 100 characters or a value longer than 500. Shorten the key or value. Fix the request
not_found
Not found
problem
404 The resource named in the path or body does not exist, or exists but is not visible to the caller's account or mode (tenancy answers 404, not 403). Used across most surfaces: payments, transactions, payouts, bills, billers, PSP applications, approvals, journals, callbacks, settlements, reports, keys, audit tenants and more. Check the id and that you are using a key of the account and environment (test/live) that owns the resource. Fix the request

Idempotency

The Idempotency-Key header that makes a retry safe.

CodeStatusMeaningWhat to doRetry
idempotency_key_required
Idempotency key required
problem
400 A request that moves money (for example POST /charges, /refunds, /payouts, /v1/payouts, /v1/bill-payments, /v1/psp/transactions, /v1/payments/{id}/refund, /v1/payments/{id}/cancel, /v1/cash-deposits) was sent without an Idempotency-Key header. Send a unique Idempotency-Key header and reuse it only when retrying the same request. Fix the request
idempotency_key_reused
Idempotency key reused
problem
409 The Idempotency-Key was already used in this account and mode for a different request (another method/path or a different body). Use a new Idempotency-Key for a new or changed request; reuse a key only to retry the identical request. Fix the request
idempotency_unavailable
Idempotency check unavailable
problem
503 The idempotency store could not be reached, so the request was not run. Retry after Retry-After (1 s) with the same Idempotency-Key. Retry as is
invalid_idempotency_key
Invalid idempotency key
problem
400 The Idempotency-Key header is longer than 255 bytes. Use a key of at most 255 bytes, such as a UUID. Fix the request
request_in_progress
Request in progress
problem
409 A request with the same Idempotency-Key is still being processed and did not finish within the wait window. Retry-After: 1 is set. Wait a moment and retry with the same Idempotency-Key and body; you will get the stored answer once the first request finishes. Retry later

Payments and refunds

Charges, hosted checkout, captures, refunds, risk and routing.

CodeStatusMeaningWhat to doRetry
lookup_used
Lookup already used
problem
409 POST /v1/cash-deposits/intents was called with a lookup that already started a deposit of another amount or currency: a lookup authorises one deposit. Look the reference up again (POST /v1/name-lookups) and start the new deposit from the new lookup_id; the same amount again answers the intent already started. Fix the request
account_suspended
Account suspended
problem
403 The account the request acts on is suspended, so it cannot transact. Returned by the account, key, OAuth, connector and service-resource endpoints that share the generic service error mapper. Contact the platform operator to reactivate the account. After an action
amount_exceeds_remaining
Refund exceeds remaining amount
problem
400, 422 POST /v1/payments/{id}/refund asked for more than the payment's amount minus what has already been refunded. Refund at most the remaining amount, or omit amount to refund all of it. Fix the request
amount_too_high
Amount above the method's maximum
problem
400 POST /charges with an amount over the maximum the method accepts in one transaction; the detail names the maximum. Split the payment, or offer a method with a higher limit (a card or a bank transfer). Fix the request
amount_too_low
Amount below the method's minimum
problem
400 POST /charges with an amount under the minimum the method accepts; the detail names the minimum. Raise the amount to the minimum, or offer another method. Fix the request
deposit_refused
Deposit refused
problem
422 A cash deposit intent was refused on its merits when created or confirmed, for example the reference lookup has expired or the amount is outside the reference's limits. The message gives the reason. Fix what the message names (look the reference up again, change the amount), then retry with a new Idempotency-Key. Fix the request
duplicate_originator_reference
Originator reference already used
problem
409 POST /v1/psp/transactions used an originator_reference that this originator has already submitted. Fetch the existing transaction with GET /v1/psp/transactions?originator_reference=, or use a new reference. Fix the request
invalid_card
Invalid card number
problem
400 A card number that is not 13 to 19 digits or fails the Luhn check (test mode, documented test cards only). Use one of the documented test card numbers; in live mode send the acquirer's card token. Fix the request
invalid_card_token
Invalid card token
problem
400 The card token is not a valid reference from the tokenizer. Send the token exactly as the acquirer's hosted fields returned it; tokens are single use where the acquirer says so. Fix the request
invalid_otp
Invalid OTP
problem
422 POST /charges/{id}/complete-action on a payment in an OTP flow was sent a code that does not match. Ask the customer to re-enter the code and submit it again. Fix the request
invalid_phone
Invalid phone number
problem
400 POST /charges with a phone number that does not match the pattern configured for the method (each mobile-money network has its own prefixes). Ask the payer to check the number, or offer the method their number belongs to. Fix the request
invalid_state
Invalid state
problem
409 The object is not in a state that allows the action: cancelling, confirming, rejecting or refunding a payment that is no longer pending (or not succeeded for a refund), changing manual-payment evidence after it left pending, cancelling a payout not pending/in transit, approving or rejecting a payout that is not waiting for approval, replaying an already replayed dead letter, a projection replay that did not match, or releasing a payout batch that is not closed (already released or sent) or whose release already waits for approval. Fetch the object, check its current status, and do not repeat the action. Fix the request
limit_exceeded
Limit exceeded
problem
422 The amount breaks a limit: the API key's currency, per-transaction or daily ceiling, or a routing rule's minimum/maximum amount (charges, payouts, PSP transactions; the problem names the rule and a rejection_id), or a bill product's daily limit per reference or per channel (bill quotes and payments), or the bank rail's daily limit per beneficiary NIB (bank payouts, rule beneficiary_daily) or per debit account (batch releases, rule debit_daily). Lower the amount or use another currency/key as the detail says, or wait for the daily limit to reset. Fix the request
live_payment
Test action on a live payment
problem
403 A hosted-checkout test action (confirm, decline, cancel, set metadata) was called on a live-mode payment. Use test actions only on test-mode payments. Fix the request
missing_otp
Missing OTP
problem
400 POST /charges/{id}/complete-action was called for a charge using the OTP flow, but the body had no otp. Send the code the customer received in the otp field. Fix the request
missing_phone
Phone number required
problem
400 POST /charges with a mobile-money source and no phone number. Send source.mobile_wallet.phone_number in international format, e.g. +258841234567. Fix the request
no_connector_binding
Payment method not enabled for this account
problem
409 The account has no connector (acquiring credentials) bound for the method, reported through the account/management service error mapping. Ask the operator to bind a connector for this method to your account. After an action
no_route
No route
problem
503 No PSP is available for this transaction right now (all eligible candidates are unavailable), so it was refused before any provider call. Returned by charges, payouts, PSP transactions, name lookups and reversals; the problem carries rule availability and a rejection_id. Retry after Retry-After (30 s) with the same Idempotency-Key. Retry later
not_a_partner
Not a partner bank or agent
problem
403 A cash-deposit intent call (/v1/cash-deposits/intents...) was made with a key whose account is not a partner bank or agent (no PSP record for the account). Use the key of an onboarded partner bank or agent account. After an action
not_receiptable
Payment has no receipt yet
problem
409 A receipt was requested (GET /v1/payments/{id}/receipt or /checkout/{id}/receipt) for a payment whose status does not have a receipt; a receipt is issued only once money has moved. Poll GET /payments/{id} and request the receipt once the payment has succeeded. After an action
not_refundable
Not refundable
problem
422 A refund submitted through POST /v1/psp/transactions was refused because the original transaction cannot be refunded, or the amount is more than what is left to refund. Check the original transaction's status and remaining refundable amount; lower the amount or do not refund. Fix the request
not_supported
Not supported
problem
422 The request was refused before any provider call because the transaction type, method, currency or country is not enabled in the capability matrix of any eligible PSP, or the chosen provider cannot do the operation (name lookup, payout, reversal), or no connector is bound for the method, or the method is simulated and the request is live (rule method_not_live). Returned by payment creation, PSP transactions, payouts, name lookups and reversals. Use a type, method, currency and country that is enabled for your account (the rule and detail fields name the failing check), or ask KulPay to enable it. Fix the request
pilot_limit
Pilot limit
problem
422 The PSP is in pilot and the live transaction breaks a pilot rule, named in the rule member: settings_missing (pilot settings not set up), merchant_not_allowed (metadata.merchant_id missing or not on the allow-list; payments only), currency (not the pilot currency), per_transaction, daily or monthly (the limit would be exceeded). Daily and monthly limits count payments, payouts, bill payments and deposit intents together. Returned by POST /charges, POST /v1/psp/transactions, POST /v1/payouts, POST /v1/bill-payments and POST /v1/cash-deposits/intents, before any provider or supplier call. Stay within the pilot setting named in rule and the detail, or ask the operator to change the pilot settings or complete go-live. After an action
raw_card_data_not_accepted
Card numbers are not accepted
problem
400 A raw card number was sent where a token is required: any card number in live mode, an undocumented one in test mode, or a test card on a live payment. KulPay never takes card numbers, so the merchant stays out of PCI DSS scope. Collect the card in the acquirer's hosted fields and send the token it returns. Fix the request
risk_declined
Declined by risk rules
problem
422 The payment, PSP transaction or payout was declined by the risk stage before any provider call; the rules that fired are kept on the record, not returned. The body carries rule "risk" and a rejection_id. Do not retry the same transaction; if you believe it is legitimate, contact support with the rejection_id. Fix the request
unsupported_currency
Currency not supported by the method
problem
400 POST /charges in a currency the chosen method is not configured to take. Charge in a currency the method supports; GET /methods lists them. Fix the request

Payouts

Sending money to a wallet or a bank account.

CodeStatusMeaningWhat to doRetry
beneficiary_name_required
Beneficiary name required
problem
400 POST /v1/payouts with method bank: beneficiary.name, the account holder's name the bank transfer carries, is missing. Nothing was debited. Send beneficiary.name with the account holder's name. Fix the request
invalid_account
Invalid account
problem
400 POST /v1/payouts with method bank: beneficiary.account is not a valid NIB (21 digits whose ISO 7064 MOD 97-10 check digits verify). Nothing was debited. Correct the beneficiary's NIB (spaces, dots and dashes are ignored) and send the payout again. Fix the request
insufficient_balance
Insufficient balance
problem
422 A balance KulPay holds for you does not cover the operation: your available balance for a payout, or a channel's float for a bill payment. Not to be confused with insufficient_funds, a payment failure_code for the payer's own wallet. Fund the balance (or lower the amount), then send the request again. After an action
invalid_method
Invalid payout method
problem
400 POST /payouts used a destination.type that is not supported. Valid types are mobile_wallet, bank_transfer, manual, crypto and cheque. Use one of the listed destination types. Fix the request
upstream_error
Provider error
problem
502 POST /v1/name-lookups reached the provider but it could not answer the beneficiary lookup. Retry the lookup later. Retry later

Selling bills (channels)

Billers, lookups, quotes and bill payments.

CodeStatusMeaningWhat to doRetry
amount_out_of_range
Amount not accepted
problem
422 The amount on a bill quote or payment is not one the product or reference accepts: it differs from a fixed price or offer price, is not one of the allowed denominations, is outside the product's min/max or the reference's exact/min/max, nothing is due on the reference, or, for an invoice, it is below the open amount when partial payments are not allowed or above it when overpayment is not allowed. The message states the accepted amount or range. Use the amount the message names, or look the reference up first to get the amount due. Fix the request
biller_unavailable
Biller unavailable
problem
503, 422 The biller or its supplier cannot take this payment. 503 with Retry-After: 60 when the supplier has a passing outage (for example a lookup timed out or the supplier is down). 422 when the biller is not accepting payments, the supplier of a quote is no longer configured, or (operator reconciliation) the supplier's transaction listing failed. On 503 retry after the Retry-After delay; on 422 choose another biller or product, or contact the operator. Retry later
fee_bearer_conflict
Fee and discount conflict
problem
422 A bill product was configured with contradictory fees: an agent-model product with a supplier discount, a payer-borne fee together with a biller discount, or a principal-model, biller-borne product with fees besides the discount. Set either the fee or the discount as the message says, never both. Fix the request
invoice_closed
Invoice closed
problem
409 A quote was requested for an invoice that is no longer open or partially paid (POST /v1/bill-payments/quotes), or a biller tried to attach a document to a cancelled or expired invoice (PUT /v1/biller/invoices/{number}/document). Look the invoice up again; it cannot be paid or changed in its current status. Fix the request
quote_expired
Quote expired
problem
422 POST /v1/bill-payments was called with a quote whose validity has passed; the quote is marked expired. Request a new quote (POST /v1/bill-payments/quotes) and pay that one. Fix the request
quote_used
Quote already used
problem
409 POST /v1/bill-payments was called with a quote that is no longer in quoted state: it was already paid or is being collected/paid, or a concurrent request took it first. Fetch the existing bill payment for this quote; to pay again, request a new quote. Fix the request
reference_not_found
Reference not found
problem
422 The biller has no customer/account for the reference: on a quote for a product that requires a lookup, or when a biller's payer is subscribed with an account_reference the biller does not know. Check the reference (meter, account or contract number) with the payer and try again. Fix the request
reference_rejected
Reference rejected by supplier
problem
422 The bill supplier refused the lookup or quote for this reference (the message carries the supplier's reason, or "the supplier refused the request"). Read the supplier's reason, correct the reference or amount, and try again. Fix the request
resend_limit
Resend limit reached
problem
422 POST /v1/bill-payments/{id}/resend was refused because the payment's tokens/receipt were already sent again 3 times. Do not resend; give the payer the receipt link or contact support. Fix the request
sms_unavailable
SMS not available
problem
503 POST /v1/bill-payments/{id}/resend asked for via "sms" on a gateway that has no SMS channel configured. Resend by email instead, or ask the operator to configure SMS. Fix the request

Billers' own API

Invoices, enrolled payers and remittances.

CodeStatusMeaningWhat to doRetry
invoice_locked
Invoice locked
problem
409 PATCH /v1/biller/invoices/{number} tried to change an invoice that is no longer open or already has a payment. Leave the invoice as is; issue a new invoice if the amount owed changed. Fix the request
owned_by_origin
Owned by its origin
problem
409 The request changes a commercial field (name, category, logo, credit account) of a biller onboarded in KulOffice, or retires it: KulOffice owns those, and KulPay takes them from its events (IO-3.4). Make the change in KulOffice; it reaches KulPay with its event. Fix the request
not_a_biller
Not a biller
problem
403 The API key's organisation is not registered as a biller on KulPay, so it cannot use the biller API (invoices, payers, subscriptions, remittances). Use a key that belongs to a biller organisation, or ask KulPay to register the organisation as a biller. After an action

Partners

PSP and biller onboarding, KYB, the portal, certification and go-live.

CodeStatusMeaningWhat to doRetry
option_locked
Option locked
problem
409 POST /v1/name-lookups/{lookup_id}/select chose another option of an ambiguous reference after a deposit already started from the lookup (IO-1.6). Keep the option the deposit started with; for another destination, look the reference up again and start a new deposit. Fix the request
already_submitted
Application already submitted
problem
409 A PSP onboarding application was submitted, or changed in a way that requires it to be a draft, after it had already been submitted. Read the application's status; it is already in review. Fix the request
application_incomplete
Application incomplete
problem
422 A PSP onboarding application was submitted with required fields missing. The error object carries an extra `missing` array listing them. Fill in the fields listed in error.missing, then submit again. Fix the request
certification_running
Certification already running
problem
409 A certification run was started for a PSP while another run for it is still in progress. Wait for the current run to finish, then start a new one. Retry later
document_tampered
KYB document altered
problem
409 A stored KYB document was downloaded and its content no longer matches the checksum recorded at upload. Treat the document as compromised: ask the partner to upload it again and report the incident. After an action
invalid_transition
Invalid transition
problem
409 A PSP lifecycle change (e.g. POST /v1/admin/psps/{id}/transitions, and other PSP operations that move its state) is not allowed from the PSP's current state; the body adds from, to and cause. Complete what cause names (for example KYB or certification) or choose a transition allowed from the current state. After an action
config_kind_retired
Configuration kind retired
problem
410 A new version, a rollback or the approval of a pending version was requested for a retired PSP configuration kind (routing, retired by F1-5: the router never read it). Its versions stay readable as history. The body names kind and successor, and the Link header (rel=successor-version) points to where the setting lives now: the global routing rules, POST /v1/routing/versions. Configure the setting at successor (for routing, prepare a version of the global routing rules naming the PSP); reject a pending version of the retired kind. After an action
live_keys_not_allowed
Live keys not allowed yet
problem
409 A live API key was requested for a PSP that has not reached pilot; until then it may only hold test keys. Use a test key until the PSP reaches pilot. After an action
not_submitted
Application not submitted
problem
409 A KYB document was uploaded (POST /v1/psps/applications/{id}/documents) to a PSP application that has not been submitted yet. Submit the application first (POST /v1/psps/applications/{id}/submit), then upload the documents. After an action
sandbox_unavailable
Sandbox unavailable
problem
409 A PSP sandbox operation was refused: the sandbox opens only when the PSP enters technical_setup, or the sandbox key was already revealed once. Wait until the PSP is in technical_setup, or issue a new test key instead of revealing the old one. After an action

Webhooks

Endpoints, deliveries and replays.

CodeStatusMeaningWhat to doRetry
account_required
Key not bound to an account
problem
403 GET /v1/events was called with a key that is not bound to any account, so there is no event log to read. Call with a key issued to an account. After an action
endpoint_disabled
Webhook endpoint disabled
problem
409 A replay or a test event was requested for a webhook endpoint that is disabled. Enable the endpoint (POST /v1/webhook-endpoints/{id}/enable), then replay or test again. After an action
missing_signature
Missing signature
problem
401 POST /v1/admin/webhooks/verify (the signature-checking helper) was called without a KulPay-Signature (or X-KulPay-Signature) header. Send the KulPay-Signature header your signer produced. Fix the request
no_secret_configured
No webhook secret configured
problem
503 POST /v1/admin/webhooks/verify was called on a gateway that has no webhook signing secret configured (neither webhook.secrets nor the legacy webhook.secret), so it cannot check a signature. Operator: configure a webhook signing secret on the gateway, then call the endpoint again. After an action
no_url
No webhook URL
problem
400 POST /v1/admin/webhooks/test was called without a ?url= query parameter and the gateway has no webhook.url configured, so there is nowhere to send the test event. Pass ?url= with the receiver URL, or set webhook.url in the gateway configuration. Fix the request

Files

Uploads and downloads.

CodeStatusMeaningWhat to doRetry
file_too_large
File too large
problem
413, 422 An uploaded file exceeds its size limit: a /v1/files upload, a payment attachment, a pushed or uploaded statement, an invoice file, an invoice document or a supplier report (5 MB for the bills files). Upload a smaller file, or split it. Fix the request
missing_file
Missing file
problem
400 A multipart upload had no file part: POST /files, or POST /v1/payments/{id}/attachments. Send the file in a multipart part named file. Fix the request
too_many_attachments
Too many attachments
problem
409 POST /v1/payments/{id}/attachments was refused because the payment already has the maximum of 8 attachments. Delete an attachment you no longer need before uploading another. After an action
unsupported_content_type
Unsupported file type
problem
415 POST /v1/payments/{id}/attachments: the uploaded file's Content-Type is not in the list allowed for the payment's method. Upload a file of an allowed type (the message names the type that was refused). Fix the request

Reconciliation and settlement

Statements, matching, settlements, the ledger and reports.

CodeStatusMeaningWhat to doRetry
callback_rejected
Statement push rejected
problem
401 A PSP pushed a statement to POST /callbacks/{psp_id}/{binding_id}/statements/{profile_id} and the request failed the binding's verification (signature, token or source IP). Sign or authenticate the push as the binding requires, from an allowed address. Fix the request
not_ready
Period not ready to close
problem
409 POST /v1/admin/reconciliation/periods/{period}/close was refused: the month has not ended, it is already closed, a close is already waiting for sign-off, the period is not YYYY-MM, or the close checklist does not pass. The body carries the checklist next to the error when it was computed. Read the message and checklist, resolve or carry forward the open items (or wait for the month to end / the pending sign-off), then request the close again. After an action
not_reversible
Journal not reversible
problem
409 POST /v1/admin/ledger/journals/{id}/reverse was refused: the journal is itself a reversal, it was already reversed, or no reason was given. Reverse only an original journal that has not been reversed, and always send a reason; to undo a reversal, post the movement again. Fix the request
not_submittable
Regulatory run not submittable
problem
409 POST /v1/admin/regulatory/runs/{id}/submit was refused because the run is blocked (the message gives the blocking detail) or was already submitted. Fix the cause in the blocking detail and generate a new run, or do nothing if it was already submitted. After an action
sample_rejected
Statement sample rejected
problem
422 POST /v1/psps/{id}/statement-profiles/preview could not parse the sample statement with the given profile; the message says why. Fix the profile (columns, formats) or the sample file and preview again. Fix the request
statement_rejected
Statement rejected
problem
422 A pushed statement file (POST /v1/statements) was received and stored as rejected: checksum missing or not matching, unverified transport signature, parse failure, or an account not covered by the profile. The message gives the reason. Fix the file or checksum per the reason and push it again. Fix the request

Operator API

Approvals, connectors, routing, keys and audit.

CodeStatusMeaningWhat to doRetry
account_not_found
Account not found
problem
404 A connector request (list, configure or delete bindings) named an account_id other than the caller's own, and no account with that id exists on this deployment. Only checked when the gateway runs with the payments store. Check the account_id, or omit it to act on your own account. Fix the request
already_decided
Approval already decided
problem
409 POST /v1/admin/approvals/{id}/decide was called on an approval request that has already been approved or rejected. Read the approval to see its outcome; no further decision is needed. Fix the request
ambiguous_keys
Existing keys are not bound to an account
problem
409 POST /v1/admin/accounts was refused because one or more keys in the deployment's static `keys:` registry have no account_id. Creating a second account would let those keys read the first account's payments. The detail lists the key ids. Set account_id on every key in the `keys:` registry, restart, then create the account again. After an action
audit_unavailable
Audit trail not available
problem
503 The audit chains, anchors, verifier or exports were requested on a deployment that runs without the payments store, the audit verifier or the audit exporter. Enable the payments store and audit trail on this deployment; retrying alone will not help. After an action
audit_unsigned
No audit signing key
problem
404 GET /v1/admin/audit/public-key was called on a deployment that has no audit signing key, so anchors and bundles are unsigned. Configure an audit signing key on the deployment if signed anchors are required. After an action
config_required
Credentials required
problem
400 A connector write (POST /v1/admin/connectors) did not carry a `config` object. Stored credentials are never returned, so every write must send the full config; an empty one would erase the live credentials. Send the full connector `config` with every write. Fix the request
connectors_disabled
Connector management unavailable
problem
501 The connector management API was called on a deployment started without a connector factory, so bindings cannot be managed at all. Run a deployment with dynamic connectors enabled; retrying will not help. After an action
invalid_connector
Invalid connector configuration
problem
400 POST /v1/admin/connectors named an unknown driver, or the driver's configuration is missing or malformed; the binding was not saved. Check the driver name and its required configuration and send it again. Fix the request
invalid_expires_at
Invalid expires_at
problem
400 POST /v1/admin/access-keys was sent with an expires_at that is not an RFC 3339 timestamp. Send expires_at as RFC 3339, e.g. 2026-12-31T23:59:59Z. Fix the request
invalid_key
Invalid key
problem
400 POST /v1/admin/access-keys described a key that cannot be built (for example an unknown mode, scope or IP allowlist entry); the message gives the reason. Fix the field named in the message. Fix the request
invalid_mode
Invalid mode
problem
400 A connector admin endpoint was given a mode other than test or live. Send mode=test or mode=live. Fix the request
invalid_phone_pattern
Invalid phone pattern
problem
400 POST /v1/sources was sent with a phone_pattern that is not a valid regular expression. Send a valid (Go RE2) regular expression. Fix the request
jobs_unavailable
Job scheduler not running
problem
503 GET /v1/admin/jobs reached a node on which the job scheduler is not running. Retry later, or query a node that runs the scheduler. Retry later
key_conflict
Key conflict
problem
409 POST /v1/admin/access-keys could not add the key to the registry, typically because a key with the same id already exists; the message gives the reason. Choose another id, or omit id to have one generated. Fix the request
missing_name
Missing name
problem
400 POST /v1/sources was sent without name. Send the source name. Fix the request
not_replayable
Callback not replayable
problem
409 POST /v1/admin/callbacks/{id}/replay was called for a provider callback that is not dead-lettered or failed. Replay only callbacks in dead_lettered or failed status; others are already processed or pending. Fix the request
range_too_large
Date range too large
problem
400 An audit-trail export was requested for a range longer than 92 days. Split the export into ranges of at most 92 days. Fix the request
same_approver
Same person cannot approve
problem
403 Four-eyes rule: the person who created a payout or an approval request tried to approve it (payout approval, POST /v1/admin/approvals/{id}/decide). Have a different person approve the request. After an action
stream_capacity
Too many live streams
problem
503 GET /admin/stats/stream (the dashboard's live stats stream) refused a new subscriber because the server already holds its maximum number of streams. Poll instead, or open the stream again after Retry-After (30 s). Retry later

Platform

Availability, rate limits, residency and internal errors.

CodeStatusMeaningWhat to doRetry
internal_error
Internal error
problem
500 Something failed inside the gateway. Every 500 has this code (the area it happened in is in the logs, under the X-Request-ID); the detail never carries the cause. Retry with the same Idempotency-Key: a money-moving call never acts twice. If it persists, contact support with the X-Request-ID; if a payment's state is unclear, read it rather than charging again. Retry as is
onboarding_unavailable
Onboarding unavailable
problem
503 POST /v1/psps/applications was called on a gateway node that runs without the database-backed payments store. Operator: run the gateway with the payments store; callers should use a node that has it. After an action
rate_limited
Rate limited
problem
429 Too many requests: the per-IP/surface limit, the API key's own per-minute limit, or too many failed authentication attempts from the same IP was exceeded. Wait for the number of seconds in Retry-After, then retry; slow down or ask for a higher key limit. Retry later
residency_unavailable
Residency not configured
problem
503 GET /v1/admin/residency was called on a node where region pinning is not configured or that has no payments store. Operator: configure residency on the node, or call a node that has it. After an action
service_unavailable
Service unavailable
problem
503 The endpoint needs the database-backed service (or the event store) and this gateway node runs without it. Also used by the GitHub secret-scanning receiver. Operator: run the gateway with the database-backed service; callers should use a node that has it. After an action
stages_unavailable
Stages unavailable
problem
503 GET /v1/transactions/{id}/stages was called on a node without the payments store. Operator: run the gateway with the payments store; callers should use a node that has it. After an action
timeline_unavailable
Timeline unavailable
problem
503 A transaction or account timeline was requested on a node without the payments store. Operator: run the gateway with the payments store; callers should use a node that has it. After an action
wrong_region
Wrong region
problem
421 The tenant's (or PSP applicant's country's) data is held in another region, so this region does not serve the request. Headers KulPay-Region and KulPay-Region-Url, and body fields region and base_url, name the right region. Send the request to the base URL of the region named in the response. Fix the request

Field codes

Inside a validation problem's errors[], each item names the field and one of these codes.

CodeMeaningWhat to do
invalid
Invalid value
Per-field code in a validation problem's errors[]: the field has a malformed value, e.g. money.currency not 3-8 characters, money.amount not a decimal, min_amount/max_amount not positive, retry_policy delays not Go durations, retry_on not 4xx/5xx/100-599, or a payment link's amount or expires_at malformed.Fix the field named in the item as its message says.
out_of_range
Value out of range
Inside a validation problem's errors[]: a numeric field is outside its allowed range: money.amount not positive or outside min_amount/max_amount, max_amount below min_amount, retry_policy values (max_attempts, backoff_multiplier and others) out of bounds, or a payment link's amount not positive or usage_limit negative.Change the field named in errors[].field to a value within the range given in the message.
required
Field required
Inside a validation problem's errors[]: a required field is missing, e.g. money.currency, money.amount (unless open_amount is true) or source.type on a charge.Send the field named in errors[].field.
restricted
Method restricted
Inside the gateway.method_restricted validation problem: the charge's source.type is not in its own allowed_sources or is in its blocked_sources.Drop the conflicting restriction or choose a source type the restrictions admit.
incompatible
Incompatible value
Per-field code in the errors[] of gateway.open_amount_requires_checkout: a charge asked for open_amount=true with a method that has no checkout page, so the customer cannot enter the amount.Use a method that presents a checkout page, or send a fixed amount.

Deprecated codes

These codes were replaced by one code per condition and are no longer sent: errors carry only the new code (there is no legacy_code any more). The old error: {code, message} member still rides along, with the new code, until its sunset: once the date is announced every error carries the Deprecation and Sunset headers (RFC 9745, RFC 8594), and from that date the member is gone. Read code, detail and the problem's own members.

Old codeNow
approvals_failedinternal_error
audit_failedinternal_error
balance_failedinternal_error
bills_failedinternal_error
callbacks_failedinternal_error
cash_deposits_failedinternal_error
close_failedinternal_error
code_mappings_failedinternal_error
conflictinvalid_state
credentials_failedinternal_error
data_transfer_failedinternal_error
encode_failedinternal_error
exceptions_failedinternal_error
export_failedinternal_error
fee_claims_failedinternal_error
fee_schedules_failedinternal_error
fetch_failedupstream_error
funds_flow_failedinternal_error
gateway.account_suspendedaccount_suspended
gateway.amount_exceeds_balanceamount_exceeds_remaining
gateway.amount_exceeds_remainingamount_exceeds_remaining
gateway.amount_too_highamount_too_high
gateway.amount_too_lowamount_too_low
gateway.callback_rejectedcallback_rejected
gateway.close_refusedinvalid_state
gateway.empty_bodyinvalid_request
gateway.enqueue_errorinternal_error
gateway.idempotency_key_requiredidempotency_key_required
gateway.internal_errorinternal_error
gateway.invalid_amountinvalid_amount
gateway.invalid_bodyinvalid_request
gateway.invalid_cardinvalid_card
gateway.invalid_card_tokeninvalid_card_token
gateway.invalid_methodinvalid_method
gateway.invalid_moneyinvalid_amount
gateway.invalid_otpinvalid_otp
gateway.invalid_phoneinvalid_phone
gateway.invalid_requestinvalid_request
gateway.invalid_stateinvalid_state
gateway.invalid_uploadinvalid_request
gateway.live_paymentlive_payment
gateway.missing_filemissing_file
gateway.missing_methodinvalid_request
gateway.missing_otpmissing_otp
gateway.missing_payment_idinvalid_request
gateway.missing_payout_idinvalid_request
gateway.missing_phonemissing_phone
gateway.missing_providerinvalid_request
gateway.missing_refund_idinvalid_request
gateway.no_connector_bindingno_connector_binding
gateway.not_foundnot_found
gateway.not_supportednot_supported
gateway.pilot_limitpilot_limit
gateway.provider_errorupstream_error
gateway.rate_limitedrate_limited
gateway.raw_card_data_not_acceptedraw_card_data_not_accepted
gateway.read_errorinternal_error
gateway.reconcile_failedinvalid_request
gateway.resolve_failedinvalid_request
gateway.service_unavailableservice_unavailable
gateway.statement_import_failedinvalid_request
gateway.stream_capacitystream_capacity
gateway.unsupported_currencyunsupported_currency
health_failedinternal_error
insufficient_fundsinsufficient_balance
internalinternal_error
invalid_bodyinvalid_request
invalid_uploadinvalid_request
jobs_failedinternal_error
keys_unavailableinternal_error
ledger_failedinternal_error
logo_failedinternal_error
onboarding_failedinternal_error
payouts_failedinternal_error
psp_failedinternal_error
psps_failedinternal_error
rand_failedinternal_error
read_errorinvalid_request
read_failedinternal_error
regulatory_failedinternal_error
replay_failedinternal_error
residency_failedinternal_error
resource_missingnot_found
reversals_failedinternal_error
reviews_failedinternal_error
risk_failedinternal_error
routing_failedinternal_error
safeguarding_failedinternal_error
server_errorinternal_error
settlement_reports_failedinternal_error
settlements_failedinternal_error
stages_failedinternal_error
statements_failedinternal_error
stream_unsupportedinternal_error
timeline_failedinternal_error
webhooks_failedinternal_error

Outcome codes

An accepted payment can still fail at the provider: the payer cancels, the wallet is short, the card is declined. The request got a 2xx; the payment moves to failed later, and its failure_code (with failure_message) says why, in the resource and in the *.failed webhook. KulPay maps every provider's own code to one vocabulary, so you write one switch for all rails; the provider's code stays in provider_code.

Each payment failure has a family: soft_decline may succeed later or with the payer's help (top up and try again); hard_decline will not succeed as asked (offer another method); error is technical, on the provider's side or the network (retrying is reasonable). The payer messages are short texts an app can show as they are.

Payment, refund and payout failures

The failure_code of a payment, a refund or a payout that the provider declined or that could not complete. One vocabulary for every rail: each provider's own code is kept in provider_code.

CodeFamilyMeaningWhat to doTell the payer
card_declined
Card declined
card
hard_decline The card issuer refused the payment without giving a more specific reason. Do not retry the same card unchanged; it is a hard decline. Show the payer a message and let them pay with another card or method.
O seu cartão foi recusado. Tente outro cartão ou outro método de pagamento.
Your card was declined. Please try another card or payment method.
insufficient_funds
Insufficient funds
card · mobile_money
soft_decline The card or wallet did not have enough balance to cover the amount. Retrying immediately will fail; it can succeed once the payer has added money. Show the payer a message and let them top up or use another method.
Saldo insuficiente. Carregue a carteira ou a conta e tente de novo.
Insufficient balance. Please top up your wallet or account and try again.
expired_card
Card expired
card
hard_decline The card is past its expiry date. Retrying with the same card will not succeed. Ask the payer to use a different card or method.
O seu cartão expirou. Use outro cartão ou outro método de pagamento.
Your card has expired. Please use another card or payment method.
incorrect_cvc
Incorrect security code or PIN
card
hard_decline The card security code (CVC) or the PIN the payer entered was wrong. Not retried automatically. A new attempt can succeed if the payer re-enters the correct details; show a message and let them try again.
Código de segurança ou PIN incorrecto. Verifique os dados e tente de novo.
The security code or PIN is incorrect. Please check and try again.
incorrect_number
Incorrect card number
card
hard_decline The card number is not valid. Not retried automatically. A new attempt can succeed if the payer corrects the card number; show a message and let them re-enter it.
O número do cartão está incorrecto. Verifique e tente de novo.
The card number is incorrect. Please check it and try again.
processing_error
Processing error
card · mobile_money
error
retriable
The provider hit an internal error while processing the payment. Transient: a new attempt can succeed. Retry later with a new payment, or let the payer try again.
Ocorreu um erro ao processar o pagamento. Tente de novo dentro de alguns minutos.
Something went wrong processing your payment. Please try again in a few minutes.
authentication_required
Authentication required
card
soft_decline The issuer requires the payer to authenticate (for example 3-D Secure), or authentication was not completed. Not retried automatically. A new attempt can succeed if the payer completes authentication; let them try again.
O seu banco pede confirmação adicional. Tente de novo e conclua a verificação.
Your bank needs extra verification. Please try again and complete the check.
do_not_honor
Do not honour
card
hard_decline The issuer declined the payment with the generic "do not honour" response. Hard decline: retrying the same card will not succeed. Ask the payer to contact their bank or use another method.
O seu banco recusou o pagamento. Contacte o banco ou use outro método.
Your bank declined the payment. Please contact your bank or use another method.
card_not_supported
Card not supported
card
hard_decline The card does not support this kind of purchase. Retrying the same card will not succeed. Let the payer use another card or method.
Este cartão não é aceite para este pagamento. Use outro cartão ou método.
This card can't be used for this payment. Please use another card or method.
currency_not_supported
Currency not supported
card
hard_decline The card cannot be charged in the payment's currency. Retrying the same card will not succeed. Let the payer use another card or method.
Este cartão não aceita pagamentos nesta moeda. Use outro cartão ou método.
This card can't pay in this currency. Please use another card or method.
fraudulent
Suspected fraud
card
hard_decline The issuer or provider blocked the payment as suspected fraud, or a dispute was opened with reason fraudulent. Do not retry. Show the payer a neutral message without mentioning fraud, and review the order before offering another method.
Não foi possível concluir o pagamento. Use outro método ou contacte o seu banco.
We couldn't complete the payment. Please use another method or contact your bank.
lost_card
Card reported lost
card
hard_decline The card has been reported lost. Do not retry. Show a neutral message and let the payer use another method.
O seu cartão foi recusado. Use outro cartão ou método de pagamento.
Your card was declined. Please use another card or payment method.
stolen_card
Card reported stolen
card
hard_decline The card has been reported stolen. Do not retry. Show a neutral message and let the payer use another method.
O seu cartão foi recusado. Use outro cartão ou método de pagamento.
Your card was declined. Please use another card or payment method.
pickup_card
Pick up card
card
hard_decline The issuer asked for the card to be retained, usually because it has been cancelled. Do not retry. Show a neutral message and let the payer use another method.
O seu cartão foi recusado. Contacte o banco ou use outro método.
Your card was declined. Please contact your bank or use another method.
restricted_card
Restricted card
card
hard_decline The card is restricted and cannot be used for this payment. Retrying the same card will not succeed. Ask the payer to contact their bank or use another method.
Este cartão tem restrições. Contacte o banco ou use outro método.
This card is restricted. Please contact your bank or use another method.
security_violation
Security or compliance block
card
hard_decline The provider blocked the payment for a security or compliance reason, such as a compliance hold or missing customer verification. Do not retry. Show a neutral message; the payer may need to contact their provider.
Não foi possível concluir o pagamento. Contacte o seu banco ou operador.
We couldn't complete the payment. Please contact your bank or provider.
transaction_not_allowed
Transaction not allowed
any
hard_decline The payer's card or wallet is not permitted to make this payment (inactive account, profile problem, wrong market), or KulPay's risk review declined it. Retrying unchanged will not succeed. Let the payer use another method; for a risk decline, review before accepting a new attempt.
Este pagamento não é permitido para a sua conta. Use outro método de pagamento.
This payment isn't allowed for your account. Please use another payment method.
try_again_later
Try again later
card
error
retriable
The issuer could not process the payment right now and asked for it to be sent again. Transient: a new attempt can succeed. Retry after a short wait or let the payer try again.
Não foi possível processar o pagamento agora. Tente de novo dentro de alguns minutos.
We couldn't process the payment right now. Please try again in a few minutes.
withdrawal_count_limit_exceeded
Too many transactions
card
soft_decline The card has reached the number of transactions allowed in the period. Retrying now will fail; it can succeed after the limit resets. Let the payer use another method or try later.
Atingiu o limite de transacções do cartão. Tente mais tarde ou use outro método.
Your card has reached its transaction limit. Please try later or use another method.
generic_decline
Payment declined
card · mobile_money
hard_decline The provider declined the payment without a specific reason. Hard decline: retrying unchanged will not succeed. Let the payer use another method or contact their provider.
O pagamento foi recusado. Use outro método ou contacte o seu banco ou operador.
The payment was declined. Please use another method or contact your bank or provider.
user_cancelled
Cancelled by the payer
mobile_money
hard_decline The payer cancelled or rejected the payment request on their phone. Not retried automatically. The payer can start a new payment if they want to; let them try again or choose another method.
Cancelou o pagamento no telemóvel. Pode tentar de novo quando quiser.
You cancelled the payment on your phone. You can try again whenever you like.
timeout
Timed out
card · mobile_money
error
retriable
The payer did not confirm in time (for example did not enter the PIN on the phone), or the provider answered too late. Transient: a new attempt can succeed. Let the payer try again and confirm promptly.
O tempo para confirmar o pagamento esgotou. Tente de novo e confirme no telemóvel.
The time to confirm the payment ran out. Please try again and confirm on your phone.
provider_unavailable
Provider unavailable
card · mobile_money
error
retriable
The mobile money operator, issuer or switch was temporarily unavailable or overloaded. Transient: a new attempt can succeed. Retry later or let the payer use another method.
O serviço está temporariamente indisponível. Tente de novo dentro de alguns minutos.
The service is temporarily unavailable. Please try again in a few minutes.
duplicate_transaction
Duplicate transaction
card · mobile_money
hard_decline The provider saw this transaction as a repeat of one it already received. Do not retry. Check whether the earlier payment succeeded before asking the payer to pay again.
Este pagamento já foi recebido. Verifique antes de pagar de novo.
This payment was already received. Please check before paying again.
network_error
Network error
any
error
retriable
The connection to the provider failed. Transient: a new attempt can succeed. Retry later or let the payer try again.
Houve uma falha de comunicação. Tente de novo dentro de alguns minutos.
There was a connection problem. Please try again in a few minutes.
limit_exceeded
Limit exceeded
card
soft_decline The amount is over a limit on the payer's card or account. Retrying the same amount now will fail; it can succeed with a smaller amount or after the limit resets. Let the payer use another method.
O valor ultrapassa o limite da sua conta. Tente um valor menor ou outro método.
The amount is over your account limit. Please try a smaller amount or another method.
account_not_found
Account not found
mobile_money
hard_decline The phone number or receiver is not valid at the provider. Retrying unchanged will not succeed. Ask the payer to check the number and try again, or use another method.
Número inválido. Verifique o número e tente de novo.
Invalid number. Please check the number and try again.
no_account
No wallet for this number
mobile_money
hard_decline The phone number has no wallet with this operator. Retrying the same number will not succeed. Let the payer use a number with a wallet or another method.
Este número não tem conta neste serviço. Use outro número ou método.
This number has no account with this service. Please use another number or method.
bank_rejected
Rejected by the bank
bank
hard_decline The bank, or the operator reviewing the bank transfer, rejected the payment. Retrying unchanged will not succeed. Ask the payer to check the transfer details or use another method.
O banco rejeitou o pagamento. Verifique os dados ou use outro método.
The bank rejected the payment. Please check the details or use another method.
amount_mismatch
Amount does not match
any
hard_decline The amount paid or sent to the provider did not match the amount expected. Retrying unchanged will not succeed. Check the amount and create a new payment for the correct value.
O valor pago não corresponde ao valor pedido. Verifique e tente de novo.
The amount paid doesn't match the amount requested. Please check and try again.
expired
Expired
any
hard_decline The payment or its reference expired before it was completed; KulPay also sets it when a payment held for risk review is not reviewed in time. This payment cannot be completed. Create a new payment if the payer still wants to pay.
O pagamento expirou. Inicie um novo pagamento.
The payment has expired. Please start a new payment.
connector_error
Could not reach the provider
any
error
retriable
KulPay could not send the request to the provider, or the provider rejected it as malformed or unauthorised. The money did not move. Usually transient: a new attempt can succeed. Retry later; if it persists, contact KulPay support.
Não foi possível processar o pagamento. Tente de novo dentro de alguns minutos.
We couldn't process the payment. Please try again in a few minutes.
cancelled
Cancelled
any
hard_decline The payment was cancelled before it completed. This payment will not complete. Create a new payment if the payer still wants to pay.
O pagamento foi cancelado. Pode iniciar um novo pagamento.
The payment was cancelled. You can start a new payment.
unknown
Unknown reason
any
unknown The payment failed, or was disputed, but the provider's reason has no mapping to a more specific code. Retry policy is unknown. Read failure_message, let the payer try again or use another method, and contact KulPay support if it repeats.
Não foi possível concluir o pagamento. Tente de novo ou use outro método.
We couldn't complete the payment. Please try again or use another method.

Refund and payout failures set by KulPay

Codes KulPay itself sets on a refund or a payout.

CodeOnMeaningWhat to doTell the payer
rejected
Rejected by an approver
any
refund, payout A refund or payout that needed a second person's approval was rejected, so it was never sent. failure_message is "Rejected by an approver." Do not retry automatically; create a new refund or payout only after resolving why it was rejected. The reserved money is released.
O reembolso ou pagamento não foi aprovado. Contacte o comerciante.
The refund or transfer was not approved. Please contact the merchant.
reversal_failed
Reversal failed
any
refund KulPay asked the provider to reverse the original transaction and the provider did not reverse it. The refund failed and the reserved amount is released. Retry the refund later or contact KulPay support.
Não foi possível devolver o dinheiro agora. O comerciante vai tentar de novo.
We couldn't return the money right now. The merchant will try again.

Bill payment failures

The failure_code of a bill payment, with failure_message carrying the biller's or supplier's reason.

CodeOnMeaningWhat to doTell the payer
rejected
Rejected by the biller
any
bill_payment The biller (supplier) refused the payment, for example because the reference or amount is not accepted. failure_message carries the biller's reason. Retrying unchanged will not succeed. Show the reason, let the payer check the reference or amount, and start a new bill payment. A payer who was already charged is refunded automatically.
A empresa recusou o pagamento. Verifique a referência e o valor e tente de novo.
The biller refused the payment. Please check the reference and amount and try again.
biller_unavailable
Biller unavailable
any
bill_payment The biller could not deliver right now (unavailable, out of float, or not authorised) and nothing was delivered. Transient: a new bill payment later can succeed. Nothing was charged; a payer charged by collection is refunded automatically.
O serviço da empresa está indisponível. Nada foi cobrado. Tente mais tarde.
The biller's service is unavailable. Nothing was charged. Please try later.
collection_failed
Payer was not charged
any
bill_payment The payment collecting the money from the payer could not be created, or it failed, was cancelled or expired. Nothing was charged and nothing was sent to the biller. Let the payer start again, possibly with another method; the collection payment's own failure_code says why.
Não foi possível cobrar o pagamento. Nada foi debitado. Tente de novo ou use outro método.
We couldn't collect the payment. Nothing was charged. Please try again or use another method.
collection_short
Collected amount too low
any
bill_payment The payer paid, but after fees the collected amount did not cover the bill. The collection is refunded automatically. Start a new bill payment; check the quoted total and fees.
O valor recebido não cobre a factura. O dinheiro será devolvido.
The amount received doesn't cover the bill. Your money will be returned.
not_processed
Never received by the biller
any
bill_payment After the waiting period the biller still had no record of the payment, so nothing was charged. A new bill payment can succeed. A payer charged by collection is refunded automatically.
A empresa não recebeu o pagamento. Nada foi cobrado. Tente de novo.
The biller never received the payment. Nothing was charged. Please try again.
resolved_failed
Failed after manual review
any
bill_payment The bill payment's outcome was uncertain and KulPay operators confirmed, with a second approval, that the biller did not deliver. The reserved money is released. Start a new bill payment if the payer still wants to pay; contact KulPay about any money collected from the payer.
Confirmámos que o pagamento não foi entregue à empresa. Tente de novo.
We confirmed the payment was not delivered to the biller. Please try again.

Cash-deposit exceptions

The code of a cash_in.exception event.

CodeOnMeaningWhat to doTell the payer
unconfirmed_deposit
Deposit not confirmed
bank
cash_deposit No bank statement line matched the reported cash deposit by the end of the arrival window; the deposit moves to status unconfirmed and nothing is credited. Check the entry id and account reported; the case is raised as an exception for review.
Ainda não confirmámos o seu depósito. Guarde o talão e contacte o balcão.
We haven't confirmed your deposit yet. Keep your receipt and contact the branch.
deposit_amount_mismatch
Deposit amount differs
bank
cash_deposit The bank statement line's amount or currency differs from the reported deposit; the deposit moves to status mismatch and nothing is credited. Compare the reported amount with statement_amount_minor; the case is raised as an exception for review.
O valor do depósito não corresponde. Guarde o talão e contacte o balcão.
The deposit amount doesn't match. Keep your receipt and contact the branch.
unidentified_deposit
Depositor not identified
bank
cash_deposit The deposit matched a statement line but its depositor reference names no KulPay account or tag; the money is held in suspense (status suspended). Supply the correct account or tag, or have the deposit returned from suspense.
Não identificámos a conta do depósito. Contacte o balcão com o seu talão.
We couldn't identify the account for your deposit. Contact the branch with your receipt.
credit_rejected
Credit rejected by the resolver
bank
cash_deposit The resolver (KulOffice) refused to credit the wallet the deposit was for (wallet_closed, limit_exceeded, customer_unknown), or an operator decided so in review; the credit to the resolver's account is taken back and the money is held in suspense (status suspended). Assign the deposit to another destination by a new lookup of the reference, or return it; a second operator approves.
Não conseguimos creditar a carteira indicada. Guarde o talão; vamos contactá-lo ou devolver o valor.
We couldn't credit the wallet you gave. Keep your receipt; we will contact you or return the money.
bill_failed
Bill not paid
bank
cash_deposit The deposit was for an invoice or a biller's product and the bill could not be paid (refused, failed at the supplier, or not quoted within 24 hours) (IO-2.5); the credit is taken back and the money is held in suspense (status suspended). Assign the deposit to another destination by a new lookup of the reference, or return it; a second operator approves.
Não conseguimos pagar a factura indicada. Guarde o talão; vamos contactá-lo ou devolver o valor.
We couldn't pay the bill you gave. Keep your receipt; we will contact you or return the money.
credit_account_invalid
Biller credit account withdrawn
bank
cash_deposit The deposit was for a biller whose credit account, named at lookup, is no longer one of its registered active accounts (IO-4.1): nothing was paid, and the money is held in suspense (status suspended). Assign the deposit by a new lookup of the reference (the biller's current account), or return it; a second operator approves.
Não conseguimos pagar a factura indicada. Guarde o talão; vamos contactá-lo ou devolver o valor.
We couldn't pay the bill you gave. Keep your receipt; we will contact you or return the money.

Webhook endpoint disabled

The disabled_reason of a webhook endpoint that stopped receiving deliveries.

CodeOnMeaningWhat to doTell the payer
paused
Paused by its owner
any
webhook_endpoint The endpoint's owner paused it; the endpoint shows status paused and receives no deliveries. Resume the endpoint when ready to receive events again. —
gone
Endpoint answered 410 Gone
any
webhook_endpoint The endpoint answered a delivery with HTTP 410, so KulPay disabled it. Fix or replace the endpoint URL, then re-enable it and replay missed deliveries. —
dead_letters
Deliveries failing for three days
any
webhook_endpoint The endpoint had dead-lettered deliveries on each of the last three days, so KulPay disabled it. Fix the endpoint, re-enable it, and replay the dead-lettered deliveries. —