Skip to main content

Troubleshooting

This page covers the integration failures verified against the sandbox during fullstack sprint 2026 testing. Each section follows the same shape: symptom (what you see), cause (why), fix (what to change). Codes referenced here are documented in the API error reference; release-level changes that affect this page are linked back to the changelog.

Quick lookup

SymptomSection
"Hash is not valid" on every requestHash signature failures
error_code: 100000 and error_code: 101000 both appearing for not-found casesNot-found errors: 100000 vs 101000
HTTP 404 with "Route or parameters is not supported" on S2S CARDS2S CARD invalid action names
CANCEL_SCHEDULE returns 404Schedule lifecycle uses DELETE_SCHEDULE
"Card token is invalid or not found." (205005) on a CREDIT callCREDIT field shape
order_id: Duplicate payment. (100000)Order ID reuse and idempotency
Use merchant test key. (220002)Sandbox vs production
3DS challenge never returns3DS doesn't redirect back
Webhook never fires, or fires but signature check failsWebhook delivery and signature verification
"Refund is not allowed" on a payment that should be refundableRefund not allowed
Connection / TLS failure from your serverTLS and IP allowlist

Authentication and signatures

Hash signature failures

Symptom. Every request returns HTTP 400 with:

{ "result": "ERROR", "error_code": 100000, "error_message": "Hash is not valid" }

You triple-checked the API key and it's correct, so it has to be the signature.

Causes (in order of frequency).

  1. The concatenation is not uppercased. Every supported operation uses SHA1(MD5(uppercase(concat))). If you skip the uppercase step the hash will look superficially correct but never verify.
  2. Fields concatenated in the wrong order. Each operation has a fixed input order (for example, Checkout purchase is order.number + order.amount + order.currency + order.description + password). Putting password first, or omitting order.description, gives a wrong hash.
  3. You concatenated formatted values, not raw values. Send order.amount as the same string in both the request and the hash input. "10.00" in the body and 10 in the hash will never match.
  4. merchant_key mixed up with password. They are different secrets: merchant_key goes in the request body; password goes only into the hash input.
  5. Test vs production password mismatch. Sandbox and production each have their own password. If you copied the wrong one you'll get this error 100% of the time.

Fix.

Generate the hash with a known-good helper and compare byte-for-byte. The shell equivalent (no dependencies) is:

CONCAT="${ORDER_NUMBER}${ORDER_AMOUNT}${ORDER_CURRENCY}${ORDER_DESCRIPTION}${PASSWORD}"
CONCAT_UPPER=$(printf "%s" "$CONCAT" | tr '[:lower:]' '[:upper:]')
MD5_HEX=$(printf "%s" "$CONCAT_UPPER" | md5) # or: | md5sum | awk '{print $1}'
HASH=$(printf "%s" "$MD5_HEX" | shasum -a 1 | awk '{print $1}')
echo "$HASH"

If your library output equals this output for the same inputs, the issue is in your inputs, not your hash function. The full per-operation input order is documented in the hash signature reference.

Per-operation hash inputs differ

For example, on S2S CARD the hash inputs are different per action:

  • GET_TRANS_STATUS, CAPTURE, VOID, CREDITVOID: SHA1(MD5(uppercase(trans_id + password)))
  • GET_TRANS_STATUS_BY_ORDER and RECURRING_SALE: SHA1(MD5(uppercase(order_id + password)))
  • DELETE_SCHEDULE, UPDATE_SCHEDULE: SHA1(MD5(uppercase(schedule_id + password)))

If you use a single helper across all S2S CARD calls without parameterising the identifier field, half your calls will fail signature verification with the same "Hash is not valid" message and you'll think the helper is broken when it isn't.


Error code disambiguation

Not-found errors: 100000 vs 101000

Symptom. You expected to match on a single code to detect not-found cases, but you're seeing both 100000 and 101000 come back depending on which call you made.

Cause. Since version 6.7.0 (in Changelog), the not-found code follows a per-entity rule, not a per-action rule. Operations against the transaction and order entities migrated to 101000. Operations against the payment, schedule, and recurring entities still return 100000.

Fix. Either match on (error_code, error_message) pairs, or use the operation you sent to disambiguate. Concretely:

If you calledExpect on not-found
Checkout GET_TRANS_STATUS by payment_id101000 + Payment does not exist.
Checkout GET_TRANS_STATUS by order_id101000 + Order does not exist.
S2S CARD GET_TRANS_STATUS by trans_id101000 + Transaction does not exist.
S2S CARD GET_TRANS_STATUS_BY_ORDER101000 + Order does not exist.
S2S CARD CAPTURE101000 + Transaction does not exist.
S2S CARD VOID100000 + Payment not found.
S2S CARD CREDITVOID100000 + Payment not found.
S2S CARD DELETE_SCHEDULE or UPDATE_SCHEDULE100000 + Schedule not found
S2S CARD RECURRING_SALE (post-validation)100000 + Incorrect transaction
Do not do a blind 100000 to 101000 upgrade

A common mistake when reading the changelog is to replace every error_code == "100000" check with error_code == "101000". That breaks VOID, CREDITVOID, schedule mutations, and recurring sale, which all still return 100000. The migration is per-action, not site-wide.

Refund not allowed

Symptom. Refund call returns:

{ "result": "ERROR", "error_code": 100000, "error_message": "Refund is not allowed" }

Causes.

  • The MID is not configured for refunds. Check the admin panel; the Payment action not supported (204005) code can also surface this from the routing layer.
  • The connector returned Refund via API is not supported. Only from the connector admin portal. (210002). Some acquirers only accept refunds initiated from their own portal.
  • The payment is not in a refundable state. Refunds require settled (208005). For a pending authorization, use VOID or REVERSAL instead, not REFUND.
  • You're calling Checkout vs S2S CARD with the wrong action. S2S CARD does not have a refund action at all (see below).

Fix. Either enable refunds on the MID, or refund via the connector portal, or use the right primitive (void for pending, refund only for settled).


S2S CARD action gotchas

S2S CARD invalid action names

Symptom. You send a POST to https://secure.payinspect.com/post with an action name you found in another gateway's docs and get:

{ "result": "ERROR", "error_code": 0, "error_message": "Route or parameters is not supported" }

HTTP status is 404, and error_code is 0 (not 100000). This is the signature of an action-name rejection at the route dispatcher, distinct from a validation rejection.

Cause. The S2S CARD action allowlist is strict. Common-sounding action names are not members.

The verified S2S CARD action allowlist:

GET_TRANS_STATUS
GET_TRANS_STATUS_BY_ORDER
CAPTURE
VOID
CREDITVOID
RECURRING_SALE
CREATE_SCHEDULE
DELETE_SCHEDULE
UPDATE_SCHEDULE

Action names that look right but are not valid (and what to do instead):

You typedWhat actually happensUse instead
REFUND404 + "Route or parameters is not supported"Refund-on-card via Checkout operation=credit; see below
CREDIT404 + "Route or parameters is not supported"Same
REVERSAL, REVERSE, CHARGEBACK404VOID for pending authorizations; refund via Checkout for settled
CANCEL_SCHEDULE, STOP_SCHEDULE, SCHEDULE_CANCEL404DELETE_SCHEDULE
GET_SCHEDULE, GET_SCHEDULE_STATUS404(no S2S CARD schedule-read action exists)
RECURRING_PAYMENT, REC_SALE, REC_PAYMENT, RECURRING_SALE_INIT404RECURRING_SALE
Lowercased or PascalCased variants (credit, Refund, etc.)404The allowlist is case-sensitive uppercase

Refunds on S2S CARD: there isn't one

Symptom. You're trying to issue a refund on a card payment that originated through S2S CARD and every action name you try returns 404.

Cause. S2S CARD has no refund action. Refunds on card payments go through Checkout's credit endpoint. The action allowlist above is exhaustive for the entities S2S CARD owns; refund is owned by Checkout.

Fix. Use Checkout:

POST /api/v1/payment/card/credit
Content-Type: application/json

{
"merchant_key": "27106696-...",
"operation": "credit",
"order_id": "your-order-id",
"order_amount": "10.00",
"order_currency": "USD",
"order_description": "Refund for order #1234",
"card_token": "<token-from-original-sale>",
"hash": "<see PX-12410>"
}

See Changelog for the card_token-in-CREDIT capability and the exact field shape.

Schedule lifecycle uses DELETE_SCHEDULE

Symptom. You're trying to cancel a recurring schedule and CANCEL_SCHEDULE, STOP_SCHEDULE, SCHEDULE_CANCEL, and CANCEL_RECURRING all 404.

Cause. Schedule cancellation is named DELETE_SCHEDULE on S2S CARD. The verb mismatch trips up most developers on first try.

Fix.

POST {{Api_url}}
Content-Type: application/x-www-form-urlencoded

action=DELETE_SCHEDULE&client_key=<key>&schedule_id=<uuid>&hash=<sha1(md5(upper(schedule_id+password)))>

If schedule_id does not exist you get 100000 + Schedule not found (no is between Schedule and not; the message wording matches the API literally, not the doc paraphrase historically used).

schedule_id must be valid UUID format. A non-UUID schedule_id returns 100000 with This is not a valid UUID. during validation, before the existence check.


Field shape gotchas

CREDIT field shape: flat fields, string card_token

Symptom. You followed an early PX-12410 example that uses a nested order.* object and an array card_token: [...], and the call returns:

{ "result": "ERROR", "error_code": 100000, "error_message": "Request data is invalid.", "errors": [...] }

Cause. The Credit endpoint expects flat order_id, order_amount, order_currency, order_description fields and a single-string card_token. Some early example snippets used the nested Checkout-purchase shape; that shape is not accepted by /payment/card/credit.

Fix.

{
"merchant_key": "27106696-...",
"operation": "credit",
"order_id": "test-10984",
"order_amount": "10.00",
"order_currency": "USD",
"order_description": "Refund for order #1234",
"card_token": "159e793cf746735e71dbd857dd71b5765adb2f76b87e1911c6655e59c0bd4b1c",
"name": "John Doe",
"hash": "<operation_hash>"
}

A correctly-shaped call with a bogus token returns the expected 205005 + Card token is invalid or not found. See Changelog.

Required fields for RECURRING_SALE

Symptom. RECURRING_SALE returns 100000 + Request data is invalid. with a list of missing fields.

Cause. The required field set for S2S CARD RECURRING_SALE is:

  • order_id
  • order_amount
  • order_description
  • recurring_first_trans_id
  • recurring_token

order_currency is optional. If validation passes but the recurring_first_trans_id plus recurring_token pair does not match a valid initial recurring transaction, you get 100000 + Incorrect transaction (post-validation).

Required fields for CREATE_SCHEDULE

Symptom. CREATE_SCHEDULE returns 100000 + Request data is invalid. with a list of missing fields.

Cause. The required field set is:

  • name
  • interval_length
  • interval_unit
  • payments_count

If you pass schedule_id it must be valid UUID format; otherwise validation fails before the existence check with This is not a valid UUID.


Idempotency and duplicates

Order ID reuse and idempotency

Symptom. Resubmitting a request returns:

{ "result": "ERROR", "error_code": 100000, "error_message": "order_id: Duplicate payment." }

Cause. The platform deduplicates by merchant_key + order_id within a 15-second window. Reusing the same order_id for a brand-new payment after that window can also be rejected, since order_id is treated as a uniqueness key per merchant.

Fix.

  • Use a unique order_id per payment attempt. Repeats are not the same as retries.
  • If you want to retry the same attempt (network blip, no response), reuse the same order_id within 15 seconds: the deduplication will return the original attempt's outcome rather than create a second one.
  • For status polling, do not POST a new authentication. Use GET_TRANS_STATUS instead; that endpoint is safe to call repeatedly.

3DS

3DS doesn't redirect back

Symptom. Payer completes the 3DS challenge in the issuer's window and never returns to your success URL, or returns to a page that hangs.

Causes.

  • term_url_3ds (S2S CARD) or success_url / cancel_url (Checkout) was wrong, unreachable, or returned a non-2xx status.
  • Override Link Target is configured on the MID and the chosen target conflicts with how your page is embedded; see Changelog.
  • The payer's browser blocked third-party cookies in the 3DS iframe.
  • Your callback handler does not return 200 OK quickly enough; some browsers race the redirect against the callback.

Fix.

  1. Confirm term_url_3ds / success_url / cancel_url are publicly reachable HTTPS URLs that return 2xx within a few seconds.
  2. If you're embedding Checkout in an iframe and the 3DS step needs to break out, expect to see the "Click Continue to proceed with the payment" wrapper from Changelog. That is intentional and required by some issuer ACS implementations.
  3. Inspect the transStatus, eci, and protocolVersion fields in the callback (Changelog) to confirm the issuer's authentication outcome. transStatus=C (challenge) without a follow-up callback indicates a stuck challenge flow.

Not acceptable to request the capture for payment not in pending status

Symptom. Capture returns 208003 + the message above.

Cause. The payment is no longer in the pending state. It may already have been captured, voided, or expired.

Fix. Call GET_TRANS_STATUS first to confirm the current state; only call capture when the payment is pending.


Webhooks

Webhook delivery and signature verification

Symptom. Either webhook never arrives, or it arrives but your signature check fails.

Causes for missing delivery.

  • The callback URL is not publicly reachable (behind a VPN, local-only, returns non-2xx).
  • Your endpoint does not respond within the gateway's timeout, triggering the retry behaviour documented in the Callbacks page.
  • The URL configured against the MID has a typo or wrong host. The platform will keep retrying against the configured value; correct it in the admin panel.

Causes for signature failures.

  • You're recomputing the hash with the wrong field set. Callback signatures use a different concatenation order than request signatures; the order is documented in the Callbacks page.
  • You're comparing the trimmed and the un-trimmed body. The signature is computed over the raw body; do not pre-parse JSON before recomputing.
  • You modified the Content-Type of the incoming request before reading the body (some frameworks coerce form-encoded bodies into JSON which mutates whitespace).

Fix. Log the raw incoming body byte-for-byte, recompute the signature, and diff. If they don't match, the concatenation is wrong. If they do match but your check still rejects, your library's hex / base64 encoding is mismatched.


Sandbox vs production

"Use merchant test key" (220002)

Symptom.

{ "result": "ERROR", "error_code": 220002, "error_message": "Use merchant test key." }

Cause. You sent a live merchant_key to the sandbox URL (or vice versa). The platform refuses to mix live and sandbox traffic.

Fix. Use the merchant test key when calling the sandbox, and the live key against production. Keep them in separate .env files; do not share env vars between environments.

Sandbox card numbers

The sandbox accepts the standard test card numbers documented in the Quickstart sandbox data section. Live cards will always fail in sandbox regardless of state.

TLS and IP allowlist

Symptom. curl: (60) SSL certificate problem: unable to get local issuer certificate from your own server, or Request from this ip is not allowed. (100000).

Causes.

  • unable to get local issuer certificate: a VPN or corporate proxy on your network is intercepting TLS with its own root CA. The sandbox certificate is valid; your machine just doesn't trust the intercepting CA.
  • Request from this ip is not allowed.: the merchant IP allowlist does not include your originating IP.

Fixes.

  • For the TLS issue in sandbox / dev only, you can use curl -k (insecure) or temporarily set NODE_TLS_REJECT_UNAUTHORIZED=0. Do not do either in production.
  • For the IP allowlist, either add your IP in the admin panel or send from an allowed IP.

See also

  • API error reference: every error_code and error_message pair with cause and fix.
  • Changelog: release entries that changed behaviour referenced above (PX-13015, PX-12410, PX-11635, PX-12416).
  • Errors and validation: the Checkout-specific narrative version of the same codes.