Overview
Every endpoint below lives under your assigned API host, which differs between sandbox and production:
| Environment | Base URL |
|---|---|
| Sandbox | https://{sandbox-host}/api |
| Production | https://{production-host}/api |
Both hosts, along with your bankMID(s) and login credentials, are issued by your WebXPay integration contact during onboarding — they aren't self-service. All request and response bodies are JSON (Content-Type: application/json), with three exceptions: the card-save flow, the one-time payment flow, and — for some merchants — the customer-present pay flow, all of which can hand control to a bank-hosted page via browser redirect. These are covered below in Save card, Pay once, and Pay — customer present.
Authentication
Every endpoint on this page requires a bearer token. Obtain one by posting your issued username and password:
{
"username": "your-issued-username",
"password": "your-issued-password"
}{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGc...",
"token_type": "bearer",
"expires_in": 3600
}Send it on every subsequent request:
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGc...
Tokens expire (expires_in seconds). Call POST /api/refresh with the current (or recently expired) bearer token to get a new one, rather than logging in again on every request.
Response envelope
Every response — success, validation error, or a bank decline — comes back as JSON with the same top-level shape, so you can write one parser for all of them.
Success
{
"status": "success",
"error": false,
"message": "...",
"data": { "...": "endpoint-specific payload" }
}Request problem
{
"status": "failure",
"error": true,
"code": "VALIDATION_ERROR",
"message": "human-readable explanation"
}Payment declined
The request was well-formed and fully processed — the bank simply said no. Always HTTP 402, so you can tell “declined” apart from “bad request” by status code alone, without parsing the body first.
{
"status": "failure",
"error": true,
"code": "CARD_PAYMENT_DECLINED",
"paymentStatus": "DECLINED",
"message": "..."
}Redirect required
A handful of flows can't finish in one request-response round trip — card entry needs a bank-hosted page, and on some accounts a card-present authentication check does too. Instead of a final result, you get a URL to send the browser to; the eventual outcome comes back later via a browser redirect to a URL you provide, base64-encoded in a result3ds query parameter, using this same envelope shape once decoded.
{
"status": "success",
"error": false,
"message": "...",
"type": "url" | "3ds",
"paymentPageUrl" | "html3ds_url": "https://..."
}| HTTP status | Meaning |
|---|---|
| 200 | Approved / succeeded, or a redirect handed back to continue the flow. |
| 402 | Payment declined by the bank/issuer — request was valid. |
| 400 / 401 / 403 / 404 / 409 / 422 | Problem with the request itself — see error codes. |
Key concepts
bankMID
Identifies which acquiring bank, currency, and processor a request routes through. WebXPay issues you one or more MIDs during onboarding — you don't choose or generate these. The specific gateway behind a MID (MPGS, CyberSource, or Paycorp) determines which features are available for cards saved under it — most importantly, MIT and authenticated CIT both need MPGS or CyberSource; Paycorp doesn't support either.
cardId
Not a secret. It's the card's first 6 and last 4 digits concatenated — e.g. a card ending in 400000…2503 has cardId "4000002503". You receive it from Save card and List saved cards, and pass it back — together with the customer's id/email and, if the same card exists under more than one bankMID, the bankMID — to identify which stored card to use. The actual payment token never leaves WebXPay's systems.
customer.id
Your own customer identifier — your database's user ID, not something WebXPay assigns. Cards are scoped to (customer.id, customer.email, MerchantId), so always pass the same pair for the same customer.
Customer-present vs. merchant-initiated
Card networks (Visa, Mastercard) distinguish charges the cardholder is actively making from ones your system fires on its own — and expect you to tell them which is which. This API mirrors that with two payment endpoints:
- Customer-present (CIT) — the customer is in your checkout flow right now, choosing a saved card to pay with. Use
/pay. - Merchant-initiated (MIT) — you're charging a saved card on your own schedule: a subscription renewal, an installment, a background top-up. Nobody is present. Use
/pay-mit.
Save card
Tokenizes a card for future use via a bank-hosted, 3-D Secure page. No payment is taken — the small verification hold WebXPay places to confirm the card is live is reversed automatically.
| Field | Type | Notes |
|---|---|---|
currency* | string | Must match the currency configured for bankMID. |
bankMID* | string | Issued by WebXPay. |
secure3dResponseURL* | string | Your page that receives the browser back after the bank's hosted page finishes. |
customer.id* | string | Your customer identifier. |
customer.email* | string | |
customer.firstName* | string | |
customer.lastName* | string | |
customer.contactNumber* | string | |
orderNumber optional | string | Your own reference, echoed back in error responses for tracing. |
{
"status": "success",
"error": false,
"message": "card capture required to complete the transaction",
"type": "url",
"paymentPageUrl": "https://.../capture/..."
}Completing the flow
- Call this endpoint. You get back a
paymentPageUrl. - Redirect the customer's browser to
paymentPageUrl— a full page redirect, not a background call. - The customer enters their card and completes 3-D Secure on the bank's hosted page. You don't build any of this UI.
- The browser is redirected back to your
secure3dResponseURL, with the outcome appended as?result3ds=<base64>(or&result3ds=if your URL already had a query string). - Base64-decode and JSON-parse
result3ds. It's the same envelope shape as every other response on this page.
{
"status": "success",
"error": false,
"message": "Card verified and saved successfully",
"instrument": "CARD",
"paymentStatus": "APPROVED",
"data": {
"card": {
"id": "4000002503",
"cardFirst": "400000",
"cardLast": "2503",
"scheme": "VISA",
"expiry": "0842",
"dateAdded": "2026-08-06T12:35:00Z"
},
"customer": { "email": "jane@example.com", "id": "2097" },
"refundStatus": true
}
}data.card.id (that's the cardId) — you'll need it for every later payment, list, or delete call against this card.Save & pay
Identical to Save card, except the moment the card is verified it's immediately charged for amount against orderNumber — one redirect flow instead of a save followed by a separate pay call.
| Field | Type | Notes |
|---|---|---|
currency* | string | |
bankMID* | string | |
secure3dResponseURL* | string | |
orderNumber* | string | Must be unique — reused values get ORDER_NUMBER_DUPLICATE. |
amount* | string | Up to 2 decimal places, e.g. "1500.00". |
customer.id* | string | |
customer.email* | string | |
customer.firstName* | string | |
customer.lastName* | string | |
customer.contactNumber* | string |
Same initial { "paymentPageUrl": ... } response and the same redirect mechanism as Save card. The decoded result3ds carries the payment outcome merged in:
{
"status": "success",
"error": false,
"message": "Card verified, saved and paid successfully",
"instrument": "CARD",
"orderNumber": "ORD-1029",
"webxOrderReference": "T64923...",
"merchantProvidedOrderNumber": "ORD-1029",
"paymentStatus": "APPROVED",
"receipt": "016153...",
"amount": "1500.00",
"data": {
"card": { "...": "same shape as Save card" },
"customer": { "email": "jane@example.com", "id": "2097" },
"refundStatus": true
}
}{
"status": "failure",
"error": true,
"code": "CARD_PAYMENT_DECLINED",
"message": "Card saved but the payment was declined",
"paymentStatus": "DECLINED",
"data": { "card": {"...": "..."}, "customer": {"...": "..."} }
}/pay once you've resolved why it declined.Pay once (no save)
A single charge against a freshly entered card — a guest checkout, a one-off invoice — that leaves nothing behind afterward. Same hosted-page mechanics as Save card (the customer enters their card on the gateway's own page, never yours), but no MerchantCustomerToken is ever created: there's no cardId in the response, and nothing to charge again later. If you might want to bill this customer again — even once — use Save & pay instead; there's no way to “upgrade” a one-time payment into a saved card after the fact.
| Field | Type | Notes |
|---|---|---|
currency* | string | Must match the currency configured for bankMID. |
bankMID* | string | Issued by WebXPay. |
secure3dResponseURL* | string | Your page that receives the browser back after the gateway's hosted page finishes. |
orderNumber* | string | Must be unique — reused values get ORDER_NUMBER_DUPLICATE. |
amount* | string | Up to 2 decimal places, e.g. "1500.00". |
customer.id* | string | |
customer.email* | string | |
customer.firstName* | string | |
customer.lastName* | string | |
customer.contactNumber* | string |
{
"currency": "LKR",
"bankMID": "999999000077001",
"secure3dResponseURL": "https://yourapp.com/payments/return",
"orderNumber": "ORD-1042",
"amount": "1500.00",
"customer": {
"id": "guest-2933",
"email": "jane@example.com",
"firstName": "Jane",
"lastName": "Perera",
"contactNumber": "+94771234567"
}
}{
"status": "success",
"error": false,
"message": "card capture required to complete the transaction",
"type": "url",
"paymentPageUrl": "https://.../capture/..."
}Completing the flow
- Call this endpoint. You get back a
paymentPageUrl. - Redirect the customer's browser to
paymentPageUrl— a full page redirect, not a background call. - The customer enters their card on the gateway's hosted page and clears 3-D Secure if the issuer asks for it. You don't build any of this UI.
- The browser is redirected back to your
secure3dResponseURL, with the outcome appended as?result3ds=<base64>— identical mechanics to Save card. - Base64-decode and JSON-parse
result3ds. Same envelope shape as everywhere else on this page.
{
"status": "success",
"error": false,
"message": "Payment completed successfully",
"instrument": "CARD",
"orderNumber": "ORD-1042",
"webxOrderReference": "T64931...",
"merchantProvidedOrderNumber": "ORD-1042",
"paymentStatus": "APPROVED",
"receipt": "016159...",
"amount": "1500.00",
"data": {
"card": { "cardFirst": "400000", "cardLast": "2503", "cardExpiry": "0842", "scheme": "VISA" },
"customer": { "email": "jane@example.com", "id": "guest-2933" }
}
}{
"status": "failure",
"error": true,
"code": "CARD_PAYMENT_DECLINED",
"message": "Payment declined",
"instrument": "CARD",
"orderNumber": "ORD-1042",
"merchantProvidedOrderNumber": "ORD-1042",
"paymentStatus": "DECLINED",
"data": { "card": {"...": "..."}, "customer": {"...": "..."} }
}Nothing is left behind either way — approved or declined, no card is saved and no cardId is issued. There's no refundStatus field here either: unlike Save card's verification hold, this charge is the transaction, not a hold to be reversed.
cardId behind; Pay — customer present if the customer already has a saved card and is choosing it at checkout.Pay — customer present (CIT)
Charges a previously saved card while the customer is actively checking out. On most accounts this is fully synchronous — no redirect, the outcome comes back directly in the response, as shown below. If your account has authenticated CIT switched on, read on past the response examples: the issuer can still ask for a live 3-D Secure check first, and that turns this call into a redirect flow.
| Field | Type | Notes |
|---|---|---|
cardId* | string | From Save card or List saved cards. |
amount* | string | Up to 2 decimal places. |
orderNumber* | string | Must be unique. |
currency* | string | |
bankMID* | string | |
secure3dResponseURL* | string | Always required by validation. Ignored on accounts without authenticated CIT enabled. On accounts that have it enabled, this becomes the return URL for the step-up redirect whenever the issuer challenges the charge. |
customer.id* | string | Must match the customer the card was saved under. |
customer.email* | string | |
authenticationRequired optional | boolean | Force a live 3-D Secure challenge on this one call, regardless of whether authenticated CIT is switched on for the merchant. See Forcing authentication per request. |
{
"cardId": "4000002503",
"amount": "1500.00",
"orderNumber": "ORD-1030",
"currency": "LKR",
"bankMID": "999999000077001",
"secure3dResponseURL": "https://yourapp.com/payments/return",
"customer": { "id": "2097", "email": "jane@example.com" }
}{
"status": "success",
"error": false,
"message": "Payment completed successfully",
"instrument": "CARD",
"orderNumber": "ORD-1030",
"webxOrderReference": "T64925...",
"merchantProvidedOrderNumber": "ORD-1030",
"paymentStatus": "APPROVED",
"receipt": "016153...",
"amount": "1500.00",
"data": {
"card": { "cardFirst": "400000", "cardLast": "2503", "cardExpiry": "0842", "scheme": "VISA" },
"customer": { "email": "jane@example.com", "id": "2097" }
}
}{
"status": "failure",
"error": true,
"code": "CARD_PAYMENT_DECLINED",
"message": "Payment declined",
"instrument": "CARD",
"orderNumber": "ORD-1030",
"webxOrderReference": "T64925...",
"merchantProvidedOrderNumber": "ORD-1030",
"paymentStatus": "DECLINED",
"receipt": null,
"data": { "card": {"...": "..."}, "customer": {"...": "..."} }
}When the issuer wants a live 3-D Secure check
By default, /pay charges the saved card immediately as a stored-credential transaction — no further authentication, just the response shown above. Ask your WebXPay integration contact to switch on authenticated CIT for your account and that changes: every /pay call against an MPGS- or CyberSource-issued card first runs a live 3-D Secure authentication against the token, and it's the card's issuer — based on their own real-time risk analysis of the transaction, not a rule WebXPay or you control — who decides what happens next:
- Frictionless. The issuer is satisfied without looping in the cardholder. Nothing changes from your side — you still get back the exact synchronous 200/402 response shown above, just with an authentication proof attached behind the scenes before the charge runs.
- Step-up challenge. The issuer wants the cardholder to actively confirm — an OTP, a banking-app approval, a biometric prompt, whatever their bank uses. Instead of a final result, you get a redirect: same mechanics as Save card's hosted page, except the customer is confirming a payment, not entering a card.
Paycorp-issued cards are exempt either way — Paycorp has no token-level 3-D Secure support, so those charges always go straight to the stored-credential path above, even with authenticated CIT switched on.
Forcing authentication per request
Authenticated CIT is normally an all-or-nothing setting for the merchant. To insist on a live 3-D Secure check for one specific call — a higher-risk order you want challenged even though most of your CIT traffic isn't — send authenticationRequired: true in the request body. It has the same effect as the merchant-wide setting for that single call, on top of whatever the merchant-wide setting already is:
{
"cardId": "4000002503",
"amount": "1500.00",
"orderNumber": "ORD-1031",
"currency": "LKR",
"bankMID": "999999000077001",
"secure3dResponseURL": "https://yourapp.com/payments/return",
"customer": { "id": "2097", "email": "jane@example.com" },
"authenticationRequired": true
}authenticationRequired: true on a Paycorp-issued card is not silently downgraded to the stored-credential path. The call fails outright with CIT_AUTHENTICATION_UNSUPPORTED_GATEWAY instead of charging the card without authentication. Omit the field (or leave it false) for Paycorp cards you're fine charging without a live challenge.On MPGS and CyberSource cards, authenticationRequired: true drives the exact same frictionless-or-step-up flow described above, whether or not authenticated CIT is switched on for the merchant.
{
"status": "success",
"error": false,
"message": "3DS redirect required to complete the transaction",
"orderNumber": "ORD-1030",
"type": "3ds",
"html3ds_url": "https://.../webx/3ds/..."
}Completing the step-up
- Redirect the customer's browser to
html3ds_url— a full page redirect, same rule as Save card: not a background call, not an iframe fetch. - The customer confirms the charge on their issuing bank's own challenge page. You don't build any of this UI, and you don't see the OTP or app approval — only the outcome.
- The browser is redirected back to your
secure3dResponseURL, with the outcome appended as?result3ds=<base64>— identical mechanics to Save card. - Base64-decode and JSON-parse
result3ds. Same envelope shape as everywhere else, now carrying the final payment outcome.
{
"status": "success",
"error": false,
"message": "Payment completed successfully",
"instrument": "CARD",
"orderNumber": "ORD-1030",
"webxOrderReference": "T64925...",
"merchantProvidedOrderNumber": "ORD-1030",
"paymentStatus": "APPROVED",
"receipt": "016153...",
"amount": "1500.00",
"data": { "card": {"...": "..."}, "customer": {"...": "..."} }
}{
"status": "failure",
"error": true,
"code": "CARD_AUTHENTICATION_FAILED",
"message": "3DS authentication failed or cancelled",
"instrument": "CARD",
"orderNumber": "ORD-1030",
"merchantProvidedOrderNumber": "ORD-1030"
}This one arrives via the redirect, so it's never an HTTP 402 in the way a straight decline is — the card was never charged because the cardholder didn't clear the challenge. Let the customer retry, or fall back to another saved card.
Errors specific to this endpoint
| Code | HTTP | Meaning |
|---|---|---|
CIT_AUTHENTICATION_UNSUPPORTED_GATEWAY | 422 | Either authenticated CIT is on for the merchant and this card's gateway is neither MPGS, CyberSource, nor Paycorp — or the request sent authenticationRequired: true against a Paycorp-issued card, which can't be authenticated at the token level. |
CARD_3DS_INITIATE_FAILED | 422 | Couldn't start the authentication with the issuer/gateway. Safe to retry. |
CARD_3DS_AUTHENTICATION_FAILED | 422 | The gateway rejected the authentication attempt itself. |
CARD_3DS_UNSUPPORTED_TOKEN | 422 | CyberSource only. This card was saved before the account supported re-authentication and is missing the identifier the challenge needs. Have the customer save the card again. |
Pay — no customer present (MIT)
Charges a saved card on your own schedule — a subscription renewal, an installment, a background top-up — with nobody in a checkout flow to authenticate. Tagged to the card network as merchant-initiated so it isn't treated as an unauthenticated charge.
bankMID — Paycorp has no scheme-level stored-credential mechanism and returns MIT_UNSUPPORTED_GATEWAY; and the card needs a reference transaction on file, which normally happens automatically.| Field | Type | Notes |
|---|---|---|
cardId* | string | |
amount* | string | Up to 2 decimal places. |
orderNumber* | string | Must be unique. |
currency* | string | |
bankMID* | string | |
mitReason* | string | One of recurring, installment, unscheduled. |
customer.id* | string | |
customer.email* | string |
No secure3dResponseURL — there's no one to redirect. This is a fully synchronous call: you get the final outcome in the response, same as a non-authenticated /pay. There is no step-up path for MIT — if the issuer wants active cardholder authentication, it declines instead, since nobody is present to complete one.
Choosing mitReason
| Value | Use when… |
|---|---|
recurring | A subscription or membership charge on a fixed schedule the customer agreed to upfront. |
installment | One payment in a pre-agreed, fixed split-payment plan. |
unscheduled | Any other card-on-file charge without a fixed schedule — e.g. pay-as-you-go top-ups or balance replenishment. |
This isn't just a label. On CyberSource-issued cards it's carried alongside a link back to the card's original authorized transaction; on MPGS-issued cards it sets the type on the standing-instruction agreement attached to the token. Both are the scheme's own mechanism for recognizing this as a pre-agreed, merchant-initiated charge — pick the value that actually matches what the cardholder agreed to, not the one that's most likely to get approved. Misrepresenting it is a scheme-rules violation, and doesn't stop a cardholder from disputing the charge.
{
"cardId": "4000002503",
"amount": "1500.00",
"orderNumber": "SUB-2026-08-ORD-4471",
"currency": "LKR",
"bankMID": "999999000077001",
"mitReason": "recurring",
"customer": { "id": "2097", "email": "jane@example.com" }
}Response shape on success or decline is identical to /pay's non-authenticated response.
Cardholder consent — get it before you tokenize
MIT only exists in card-network rules because the cardholder agreed in advance to be charged without being present for each individual charge. That agreement is yours to obtain and keep — WebXPay doesn't collect or store consent evidence on your behalf, and this API has no field for it. Before the card leaves your Save card flow for its intended MIT use:
- Disclose what you're signing them up for — the amount and cadence for a subscription, the full schedule for an installment plan, or plainly that it's a card-on-file for occasional/unscheduled charges — in language the customer actually sees and accepts, not buried in a general terms-of-service link.
- Keep proof — the exact consent text shown, a timestamp, and enough context (order/session id, IP) to reconstruct it later. If a cardholder disputes a MIT charge, this evidence is what stands between you and a chargeback you can't contest — the scheme rules put that burden on the merchant, not the acquirer or WebXPay.
- Re-confirm if terms change — a price increase or a new billing cycle on an existing subscription needs fresh notice/consent under most scheme rules, not a silent change to the next MIT charge.
Why MIT charges “just work” once a card is saved
Every successful card-verification or charge — the small hold at Save card, a Save & pay, a CIT /pay — leaves behind a reference to that authorized transaction against the saved token. /pay-mit chains new charges off the most recent one of those automatically; there's no separate “enroll this card for MIT” call to make. The one case where it's missing is a card tokenized before your account had this capability at all — those return MIT_NO_REFERENCE_TRANSACTION, and the fix is simply having the customer save the card again.
A typical subscription flow
- Get consent at signup. Show the billing terms and capture agreement, then call Save card or Save & pay if the first cycle is charged immediately.
- Store the
cardIdagainst the subscription record — you'll reuse it for every future cycle. - On each renewal date, call
/pay-mitwith a fresh uniqueorderNumber, the storedcardId, andmitReason: "recurring". - Handle declines out of band. There's no in-line retry or challenge to fall back to here — notify the customer, apply your own dunning/retry schedule, and if declines persist, ask them to come back through a CIT flow (or re-save the card) to refresh it.
Limitations
- No interactive fallback. If the issuer wants active authentication, it just declines — MIT has no redirect or challenge path to resolve that in the same call, by design.
- Paycorp-routed cards are never eligible, regardless of the
mit_enabledsetting. - Expired cards still fail normally —
CARD_EXPIREDapplies the same as CIT. - Amounts should stay consistent with what was agreed. A charge that looks unrelated to the original consented pattern is exactly what issuer risk models are tuned to flag.
- Enablement is per merchant, not automatic.
mit_enabledrequires a registered recurring/installment agreement with your acquiring bank — WebXPay can't turn it on without that in place.
Errors specific to this endpoint
| Code | HTTP | Meaning |
|---|---|---|
MIT_NOT_ENABLED | 403 | MIT isn't turned on for this merchant yet. |
MIT_UNSUPPORTED_GATEWAY | 422 | This card's bankMID routes through Paycorp, which has no MIT support. |
MIT_NO_REFERENCE_TRANSACTION | 422 | No prior authorized transaction on file for this card to chain against. Have the customer re-save the card via Save card. |
List saved cards
Returns every active card saved for a customer.
| Field | Type |
|---|---|
customer.id* | string |
customer.email* | string |
{
"status": "success",
"error": false,
"message": "Cards retrieved successfully",
"instrument": "CARD",
"customer": { "id": "2097", "email": "jane@example.com" },
"data": {
"cards": [
{
"bankMID": "999999000077001",
"cardId": "4000002503",
"cardFirst": "400000",
"cardLast": "2503",
"cardExpiry": "0842",
"cardScheme": "VISA"
}
]
}
}A card past its expiry carries an extra "note" field rather than being left out — decide whether to still display it.
Delete a card
Permanently removes a saved card. Irreversible — the customer would need to save it again to use it after this.
| Field | Type | Notes |
|---|---|---|
cardId* | string | |
customerId* | string | Flat field here, not nested under customer. |
customerEmail* | string | Flat field here, not nested under customer. |
bankMID optional | string | Only required if the same card is saved under more than one bankMID for this customer. |
{
"cardId": "4000002503",
"customerId": "2097",
"customerEmail": "jane@example.com"
}{
"status": "success",
"error": false,
"message": "Card deleted successfully",
"instrument": "CARD",
"customerId": "2097",
"cardId": "4000002503"
}bankMIDs and you didn't pass one, you'll get CARD_MULTIPLE_MATCHES (409) — pass bankMID to disambiguate.Error codes
Machine-readable code values you can safely branch on, across every endpoint on this page.
| Code | HTTP | Meaning |
|---|---|---|
UNAUTHORIZED | 401 | Missing, invalid, or expired bearer token. |
VALIDATION_ERROR | 422 | A required field is missing or malformed — see message for specifics. |
INVALID_MID | 422 | bankMID not recognized or not active. |
CURRENCY_VALIDATION_FAILED | 422 | currency doesn't match what's configured for that bankMID. |
MERCHANT_NOT_FOUND | 404 | Shouldn't happen in normal use — contact WebXPay if you see this. |
ORDER_NUMBER_DUPLICATE | 409 | orderNumber was already used for this merchant. |
CARD_NOT_FOUND | 404 | No saved card matches the given cardId + customer (+ bankMID). |
CARD_EXPIRED | 422 | The saved card is past its expiry — have the customer save a new one. |
CARD_PAYMENT_DECLINED | 402 | The bank/issuer declined the charge. |
CARD_MULTIPLE_MATCHES | 409 | (Delete only) Same card under multiple bankMIDs — pass bankMID to disambiguate. |
CARD_DELETE_FAILED | 422 | The bank/gateway refused the delete request. |
CIT_AUTHENTICATION_UNSUPPORTED_GATEWAY | 422 | (Pay only) Authenticated CIT is enabled for this merchant, or the request sent authenticationRequired: true, and this card's gateway doesn't support it (e.g. Paycorp). |
CARD_3DS_INITIATE_FAILED | 422 | (Pay only) Couldn't start the authenticated-CIT 3DS check with the issuer/gateway. |
CARD_3DS_AUTHENTICATION_FAILED | 422 | (Pay only) The gateway rejected the 3DS authentication attempt itself. |
CARD_3DS_UNSUPPORTED_TOKEN | 422 | (Pay only, CyberSource) Saved card can't be re-authenticated — have the customer save it again. |
CARD_AUTHENTICATION_FAILED | via result3ds redirect | (Pay only) The cardholder failed or abandoned the 3DS step-up challenge. |
MIT_NOT_ENABLED | 403 | (Pay-MIT only) MIT isn't enabled for this merchant. |
MIT_UNSUPPORTED_GATEWAY | 422 | (Pay-MIT only) Card's bankMID routes through Paycorp, which MIT doesn't support. |
MIT_NO_REFERENCE_TRANSACTION | 422 | (Pay-MIT only) No prior authorized transaction on file — re-save the card. |