X
WEBXPAY
Developers
Tokenized API · V2
WEBXPAY Developer Guide

WEBXPAY Tokenized Setup Guide V2

Save a card, charge it while the customer is present, or charge it later on your own schedule. This guide covers every card endpoint end to end, with real request and response bodies.

v2 · CardsSeptember 1, 2026JSON APIBearer authentication

Overview

Every endpoint below lives under your assigned API host, which differs between sandbox and production:

EnvironmentBase URL
Sandboxhttps://{sandbox-host}/api
Productionhttps://{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:

POST/api/authno auth required
Request
{
  "username": "your-issued-username",
  "password": "your-issued-password"
}
Response200
{
  "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 statusMeaning
200Approved / succeeded, or a redirect handed back to continue the flow.
402Payment declined by the bank/issuer — request was valid.
400 / 401 / 403 / 404 / 409 / 422Problem 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.
If you're planning to bill a saved card via MIT at all — even once — read Pay — no customer present before you build your save flow. The cardholder consent and reference-transaction requirements have to be satisfied at save time, not retrofitted later.

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.

POST/api/v2/merchant/hc/cards/savebearer required
FieldTypeNotes
currency*stringMust match the currency configured for bankMID.
bankMID*stringIssued by WebXPay.
secure3dResponseURL*stringYour page that receives the browser back after the bank's hosted page finishes.
customer.id*stringYour customer identifier.
customer.email*string
customer.firstName*string
customer.lastName*string
customer.contactNumber*string
orderNumber optionalstringYour own reference, echoed back in error responses for tracing.
Response200
{
  "status": "success",
  "error": false,
  "message": "card capture required to complete the transaction",
  "type": "url",
  "paymentPageUrl": "https://.../capture/..."
}

Completing the flow

  1. Call this endpoint. You get back a paymentPageUrl.
  2. Redirect the customer's browser to paymentPageUrl — a full page redirect, not a background call.
  3. The customer enters their card and completes 3-D Secure on the bank's hosted page. You don't build any of this UI.
  4. 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).
  5. Base64-decode and JSON-parse result3ds. It's the same envelope shape as every other response on this page.
Decoded result3ds — card saved
{
  "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
  }
}
Keep data.card.id (that's the cardId) — you'll need it for every later payment, list, or delete call against this card.
Planning to charge this card later via MIT — a subscription, an installment plan, background top-ups? The verification hold above is itself an authorized transaction, so a card saved through this endpoint already carries the reference WebXPay needs to originate MIT charges against it later, with no extra step. What you still have to do yourself, before the customer leaves this flow, is capture their explicit consent to be billed that way — see Cardholder consent.

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.

POST/api/v2/merchant/hc/cards/saveandpaybearer required
FieldTypeNotes
currency*string
bankMID*string
secure3dResponseURL*string
orderNumber*stringMust be unique — reused values get ORDER_NUMBER_DUPLICATE.
amount*stringUp 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:

Decoded result3ds — approved
{
  "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
  }
}
Decoded result3ds — card saved, payment declined
{
  "status": "failure",
  "error": true,
  "code": "CARD_PAYMENT_DECLINED",
  "message": "Card saved but the payment was declined",
  "paymentStatus": "DECLINED",
  "data": { "card": {"...": "..."}, "customer": {"...": "..."} }
}
The card is saved either way — a declined first payment doesn't undo the tokenization. Re-attempt the charge with /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.

POST/api/v2/merchant/hc/cards/pay-onetimebearer required
FieldTypeNotes
currency*stringMust match the currency configured for bankMID.
bankMID*stringIssued by WebXPay.
secure3dResponseURL*stringYour page that receives the browser back after the gateway's hosted page finishes.
orderNumber*stringMust be unique — reused values get ORDER_NUMBER_DUPLICATE.
amount*stringUp to 2 decimal places, e.g. "1500.00".
customer.id*string
customer.email*string
customer.firstName*string
customer.lastName*string
customer.contactNumber*string
Request
{
  "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"
  }
}
Response200
{
  "status": "success",
  "error": false,
  "message": "card capture required to complete the transaction",
  "type": "url",
  "paymentPageUrl": "https://.../capture/..."
}

Completing the flow

  1. Call this endpoint. You get back a paymentPageUrl.
  2. Redirect the customer's browser to paymentPageUrl — a full page redirect, not a background call.
  3. 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.
  4. The browser is redirected back to your secure3dResponseURL, with the outcome appended as ?result3ds=<base64> — identical mechanics to Save card.
  5. Base64-decode and JSON-parse result3ds. Same envelope shape as everywhere else on this page.
Decoded result3ds — approved
{
  "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" }
  }
}
Decoded result3ds — declined
{
  "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.

Choosing between the three payment endpoints: use Pay once here for a card you'll never see again; Save & pay if you want this same charge to also leave a reusable 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.

POST/api/v2/merchant/hc/cards/paybearer required
FieldTypeNotes
cardId*stringFrom Save card or List saved cards.
amount*stringUp to 2 decimal places.
orderNumber*stringMust be unique.
currency*string
bankMID*string
secure3dResponseURL*stringAlways 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*stringMust match the customer the card was saved under.
customer.email*string
authenticationRequired optionalbooleanForce 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.
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" }
}
Response — approved200
{
  "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" }
  }
}
Response — declined402
{
  "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
}
Unlike the merchant-wide setting, an explicit 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.

Response — step-up required200
{
  "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

  1. 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.
  2. 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.
  3. The browser is redirected back to your secure3dResponseURL, with the outcome appended as ?result3ds=<base64> — identical mechanics to Save card.
  4. Base64-decode and JSON-parse result3ds. Same envelope shape as everywhere else, now carrying the final payment outcome.
Decoded result3ds — approved after step-up
{
  "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": {"...": "..."} }
}
Decoded result3ds — authentication failed or abandoned
{
  "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

CodeHTTPMeaning
CIT_AUTHENTICATION_UNSUPPORTED_GATEWAY422Either 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_FAILED422Couldn't start the authentication with the issuer/gateway. Safe to retry.
CARD_3DS_AUTHENTICATION_FAILED422The gateway rejected the authentication attempt itself.
CARD_3DS_UNSUPPORTED_TOKEN422CyberSource 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.

Three things have to be true before you can use this endpoint: your merchant account must have MIT enabled by WebXPay (only merchants with a recurring/installment agreement registered with their acquiring bank qualify); the card must have been saved under an MPGS- or CyberSource-routed 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.
POST/api/v2/merchant/hc/cards/pay-mitbearer required
FieldTypeNotes
cardId*string
amount*stringUp to 2 decimal places.
orderNumber*stringMust be unique.
currency*string
bankMID*string
mitReason*stringOne 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

ValueUse when…
recurringA subscription or membership charge on a fixed schedule the customer agreed to upfront.
installmentOne payment in a pre-agreed, fixed split-payment plan.
unscheduledAny 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.

Request
{
  "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.

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

  1. 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.
  2. Store the cardId against the subscription record — you'll reuse it for every future cycle.
  3. On each renewal date, call /pay-mit with a fresh unique orderNumber, the stored cardId, and mitReason: "recurring".
  4. 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_enabled setting.
  • Expired cards still fail normally — CARD_EXPIRED applies 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_enabled requires a registered recurring/installment agreement with your acquiring bank — WebXPay can't turn it on without that in place.

Errors specific to this endpoint

CodeHTTPMeaning
MIT_NOT_ENABLED403MIT isn't turned on for this merchant yet.
MIT_UNSUPPORTED_GATEWAY422This card's bankMID routes through Paycorp, which has no MIT support.
MIT_NO_REFERENCE_TRANSACTION422No 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.

POST/api/v2/merchant/cards/getbearer required
FieldType
customer.id*string
customer.email*string
Response200
{
  "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.

DELETE/api/v2/merchant/cards/deletebearer required
FieldTypeNotes
cardId*string
customerId*stringFlat field here, not nested under customer.
customerEmail*stringFlat field here, not nested under customer.
bankMID optionalstringOnly required if the same card is saved under more than one bankMID for this customer.
Request
{
  "cardId": "4000002503",
  "customerId": "2097",
  "customerEmail": "jane@example.com"
}
Response200
{
  "status": "success",
  "error": false,
  "message": "Card deleted successfully",
  "instrument": "CARD",
  "customerId": "2097",
  "cardId": "4000002503"
}
If the card exists under multiple 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.

CodeHTTPMeaning
UNAUTHORIZED401Missing, invalid, or expired bearer token.
VALIDATION_ERROR422A required field is missing or malformed — see message for specifics.
INVALID_MID422bankMID not recognized or not active.
CURRENCY_VALIDATION_FAILED422currency doesn't match what's configured for that bankMID.
MERCHANT_NOT_FOUND404Shouldn't happen in normal use — contact WebXPay if you see this.
ORDER_NUMBER_DUPLICATE409orderNumber was already used for this merchant.
CARD_NOT_FOUND404No saved card matches the given cardId + customer (+ bankMID).
CARD_EXPIRED422The saved card is past its expiry — have the customer save a new one.
CARD_PAYMENT_DECLINED402The bank/issuer declined the charge.
CARD_MULTIPLE_MATCHES409(Delete only) Same card under multiple bankMIDs — pass bankMID to disambiguate.
CARD_DELETE_FAILED422The bank/gateway refused the delete request.
CIT_AUTHENTICATION_UNSUPPORTED_GATEWAY422(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_FAILED422(Pay only) Couldn't start the authenticated-CIT 3DS check with the issuer/gateway.
CARD_3DS_AUTHENTICATION_FAILED422(Pay only) The gateway rejected the 3DS authentication attempt itself.
CARD_3DS_UNSUPPORTED_TOKEN422(Pay only, CyberSource) Saved card can't be re-authenticated — have the customer save it again.
CARD_AUTHENTICATION_FAILEDvia result3ds redirect(Pay only) The cardholder failed or abandoned the 3DS step-up challenge.
MIT_NOT_ENABLED403(Pay-MIT only) MIT isn't enabled for this merchant.
MIT_UNSUPPORTED_GATEWAY422(Pay-MIT only) Card's bankMID routes through Paycorp, which MIT doesn't support.
MIT_NO_REFERENCE_TRANSACTION422(Pay-MIT only) No prior authorized transaction on file — re-save the card.