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
| Symptom | Section |
|---|---|
"Hash is not valid" on every request | Hash signature failures |
error_code: 100000 and error_code: 101000 both appearing for not-found cases | Not-found errors: 100000 vs 101000 |
HTTP 404 with "Route or parameters is not supported" on S2S CARD | S2S CARD invalid action names |
CANCEL_SCHEDULE returns 404 | Schedule lifecycle uses DELETE_SCHEDULE |
"Card token is invalid or not found." (205005) on a CREDIT call | CREDIT field shape |
order_id: Duplicate payment. (100000) | Order ID reuse and idempotency |
Use merchant test key. (220002) | Sandbox vs production |
| 3DS challenge never returns | 3DS doesn't redirect back |
| Webhook never fires, or fires but signature check fails | Webhook delivery and signature verification |
"Refund is not allowed" on a payment that should be refundable | Refund not allowed |
| Connection / TLS failure from your server | TLS 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).
- 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. - 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). Puttingpasswordfirst, or omittingorder.description, gives a wrong hash. - You concatenated formatted values, not raw values. Send
order.amountas the same string in both the request and the hash input."10.00"in the body and10in the hash will never match. merchant_keymixed up withpassword. They are different secrets:merchant_keygoes in the request body;passwordgoes only into the hash input.- 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.
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_ORDERandRECURRING_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 called | Expect on not-found |
|---|---|
Checkout GET_TRANS_STATUS by payment_id | 101000 + Payment does not exist. |
Checkout GET_TRANS_STATUS by order_id | 101000 + Order does not exist. |
S2S CARD GET_TRANS_STATUS by trans_id | 101000 + Transaction does not exist. |
S2S CARD GET_TRANS_STATUS_BY_ORDER | 101000 + Order does not exist. |
S2S CARD CAPTURE | 101000 + Transaction does not exist. |
S2S CARD VOID | 100000 + Payment not found. |
S2S CARD CREDITVOID | 100000 + Payment not found. |
S2S CARD DELETE_SCHEDULE or UPDATE_SCHEDULE | 100000 + Schedule not found |
S2S CARD RECURRING_SALE (post-validation) | 100000 + Incorrect transaction |
100000 to 101000 upgradeA 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 apendingauthorization, 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 typed | What actually happens | Use instead |
|---|---|---|
REFUND | 404 + "Route or parameters is not supported" | Refund-on-card via Checkout operation=credit; see below |
CREDIT | 404 + "Route or parameters is not supported" | Same |
REVERSAL, REVERSE, CHARGEBACK | 404 | VOID for pending authorizations; refund via Checkout for settled |
CANCEL_SCHEDULE, STOP_SCHEDULE, SCHEDULE_CANCEL | 404 | DELETE_SCHEDULE |
GET_SCHEDULE, GET_SCHEDULE_STATUS | 404 | (no S2S CARD schedule-read action exists) |
RECURRING_PAYMENT, REC_SALE, REC_PAYMENT, RECURRING_SALE_INIT | 404 | RECURRING_SALE |
Lowercased or PascalCased variants (credit, Refund, etc.) | 404 | The 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_idorder_amountorder_descriptionrecurring_first_trans_idrecurring_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:
nameinterval_lengthinterval_unitpayments_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_idper 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_idwithin 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_STATUSinstead; 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) orsuccess_url/cancel_url(Checkout) was wrong, unreachable, or returned a non-2xx status.Override Link Targetis 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 OKquickly enough; some browsers race the redirect against the callback.
Fix.
- Confirm
term_url_3ds/success_url/cancel_urlare publicly reachable HTTPS URLs that return2xxwithin a few seconds. - 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.
- Inspect the
transStatus,eci, andprotocolVersionfields 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-Typeof 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 setNODE_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_codeanderror_messagepair 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.