# Sandbox test cards

One scenario per sandbox classification test card, with the request to send, the classification to expect on the response, and the behavior each card lets you observe.

**Category:** Integration

**Last reviewed:** 12 September 2026

# Sandbox test cards

The sandbox publishes eight test cards that classify as something specific: personal debit on two rails, prepaid, commercial on two rails, government purchase, healthcare, and EBT. This guide walks one scenario per card. Each scenario gives you the request to send, the classification to expect on the response, and at least one downstream behavior the card lets you observe and verify.

The card numbers, expiries, and verification values here are the ones the sandbox testing guide's test-card table publishes. They're network test numbers: issued to nobody, backed by no funds, and never sent to a card network.

## How the sandbox classifies a card

Syntch classifies every card from its BIN, the leading digits of the number, by looking it up in the platform's BIN database. The sandbox test cards sit in front of that database as an exact-match overlay: when the **full number** you send is one of the eight, the lookup answers with the classification the card declares instead of reading the database.

Three consequences follow, and each scenario below depends on at least one of them:

- **The match is on the whole number, never on a prefix.** A card that shares a test card's leading digits resolves from the BIN database like any other card. Only the exact number classifies as shown.
- **It classifies the same way everywhere.** The overlay applies in every environment and for every merchant, so a merchant in **Processor Test** mode sees the same classification for the same number that a **Loopback** merchant sees, and so does a real processor test host for the numbers taken from a processor's certification set.
- **The card decides its classification, not the outcome.** The sandbox still picks the result from the transaction amount, the billing ZIP, the CVV, and any control field you send, exactly as the sandbox testing guide describes. A test card that classifies as debit is approved or declined by the amount, the same as any other card.

The one exception is the EBT card, which takes no amount trigger. Its scenario says so.

## What every scenario sends and reads

Every scenario is a `POST` to `/api/transactions` on a sandbox merchant whose **Processor Mode** is **Loopback**. The requests differ only in the card, the amount, and the fields the scenario is exercising. Substitute `YOUR_API_KEY` with a sandbox key.

Send the number as digits only, with the expiry the card declares, and the card's CVV in `cvv` as a number. Every card-entry surface on Syntch collects the CVV, the CVV-keyed verification triggers in the sandbox testing guide read it, and a request without it exercises less than a real one: send it on every scenario, as the examples do.

Three places on the response carry the classification, and the scenarios refer to them by name:

- **`cardData.binData`** is the classification the platform stored for the card: `brand`, `type` (`Credit` or `Debit`), `category`, `issuedEntity` (`Personal` or `Commercial`), `fundingSource` (`Credit`, `Debit`, or `Prepaid`), and `flags` (`commercial`, `government`, `healthcare`, `prepaid`, `regulated`). The `issuer.organization` names the sandbox issuer rather than a bank, which is how you can tell a sandbox classification from a BIN database match on a stored transaction.
- **`responseData`** is what the sandbox processor answered: `cardIndicator` echoes the brand, `detailedProductId` echoes the card type, `routingIndicator` says which rail the sale was processed on, and `processorResponseDetail.isCommercialCard` is `true` for a commercial or government card.
- **`loopbackSimulation`** is the trace of what the sandbox saw. A classification card adds an entry with a `family` of `TestCard` and a `catalogId` naming the card, such as `visa-debit`. The entry never carries the number.

The response is a full transaction, so the examples below show only the fields the scenario is about.

## Visa debit

Number `4002960001111116`, expiry `12/2030`, CVV `123`. Classifies as personal debit on the Visa rail.

```bash
curl -X POST "https://your-gateway-host/api/transactions" \
  -H "api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "transactionType": "Sale",
    "idempotencyKey": "test-cards-visa-debit-0001",
    "cardData": {
      "cardNumber": "4002960001111116",
      "nameOnCard": "Jane Doe",
      "expirationMonth": 12,
      "expirationYear": 2030,
      "cvv": 123,
      "isDebitRouting": true
    },
    "invoiceData": {
      "amounts": { "base": 10.00, "total": 10.00 }
    }
  }'
```

Expect on the response:

```json
{
  "cardData": {
    "binData": {
      "brand": "Visa",
      "type": "Debit",
      "category": "classic",
      "issuedEntity": "Personal",
      "fundingSource": "Debit",
      "flags": { "commercial": false, "government": false, "healthcare": false, "prepaid": false, "regulated": false }
    }
  },
  "responseData": {
    "resultCode": "Ok",
    "cardIndicator": "Visa",
    "detailedProductId": "Debit",
    "routingIndicator": "ProcessedAsDebit",
    "processorResponseDetail": { "isCommercialCard": false }
  },
  "loopbackSimulation": {
    "entries": [
      { "family": "TestCard", "catalogId": "visa-debit", "matchedOn": "Published test card", "isDefault": false }
    ]
  }
}
```

What the card lets you observe:

- **Debit routing on the response.** With `isDebitRouting` set to `true` on the card, as a PIN debit request sends it, `routingIndicator` reads `ProcessedAsDebit`. Send the same request without it and a keyed card reads `ProcessedAsCredit`. Nothing in the classification sets the flag for you: a debit card keyed without PIN data is a signature debit sale and reads as credit-routed, which is what a real processor reports. The `loopback.routing` control field outranks both.
- **Card acceptance policy.** On a merchant whose card acceptance policy has **Accept debit cards** turned off, the request is refused with HTTP 400 and the code `Transactions:CardNotAcceptedByMerchantPolicy`; the `reason` datum reads `FundingSource`, and no transaction is created. Turn the setting back on and the same request is approved.
- **Surcharge.** On a merchant that's actively surcharging, `surchargeResult.surcharged` is `false` with `ineligibilityReason` of `DebitCard`, and `invoiceData.amounts.surcharge` stays empty: debit cards are never surcharged. A credit card on the same merchant is surcharged.
- **Convenience fee.** A configured convenience fee is assessed on this card exactly as it's assessed on a credit card. The merchant's debit exemption toggle is stored, but no runtime path applies it today, so don't build a test that expects the fee to be withheld from a debit card.

## Mastercard debit

Number `5555531000000010`, expiry `12/2030`, CVV `123`. Classifies as personal debit on the Mastercard rail.

```bash
curl -X POST "https://your-gateway-host/api/transactions" \
  -H "api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "transactionType": "Sale",
    "idempotencyKey": "test-cards-mastercard-debit-0001",
    "cardData": {
      "cardNumber": "5555531000000010",
      "nameOnCard": "Jane Doe",
      "expirationMonth": 12,
      "expirationYear": 2030,
      "cvv": 123
    },
    "invoiceData": {
      "amounts": { "base": 10.00, "total": 10.00 }
    }
  }'
```

Expect `cardData.binData` with `brand` of `Mastercard`, `type` of `Debit`, `category` of `standard`, `issuedEntity` of `Personal`, `fundingSource` of `Debit`, and every flag `false`. On `responseData`, `cardIndicator` reads `Mastercard`, `detailedProductId` reads `Debit`, and `routingIndicator` reads `ProcessedAsCredit`, because this request was keyed without `isDebitRouting`. The trace entry's `catalogId` is `mastercard-debit`.

What the card lets you observe is the same set as the Visa debit card, on the other rail: the **Accept debit cards** refusal, the `DebitCard` surcharge ineligibility, the convenience fee assessed as configured, and `ProcessedAsDebit` once you add `isDebitRouting`. Send this card as well as the Visa one, because several of the sandbox's amount triggers are brand-scoped and a client that has only ever sent one debit brand hasn't exercised its own brand handling.

## Mastercard prepaid

Number `5312411232145699`, expiry `12/2030`, CVV `123`. Classifies as a prepaid card on the debit rail.

```bash
curl -X POST "https://your-gateway-host/api/transactions" \
  -H "api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "transactionType": "Sale",
    "idempotencyKey": "test-cards-mastercard-prepaid-0001",
    "cardData": {
      "cardNumber": "5312411232145699",
      "nameOnCard": "Jane Doe",
      "expirationMonth": 12,
      "expirationYear": 2030,
      "cvv": 123
    },
    "invoiceData": {
      "amounts": { "base": 2.78, "total": 2.78 }
    },
    "customFields": [
      { "name": "loopback.prepaidIndicator", "value": "P" }
    ]
  }'
```

Expect `cardData.binData` with `brand` of `Mastercard`, `type` of `Debit`, `category` of `prepaid`, `fundingSource` of `Prepaid`, and `flags.prepaid` of `true`. Prepaid isn't a third rail: the card type stays `Debit`, and the prepaid flag is what moves the funding source. The trace entry's `catalogId` is `mastercard-prepaid`.

What the card lets you observe:

- **Partial approval.** The request above pairs the card with the sandbox's partial-approval amount table. The `loopback.prepaidIndicator` control field set to `P` (or `loopback.partialAuthIndicator` set to `true`) selects that table, and `2.78` is one of its amounts: the response comes back with `resultCode` of `Partial`, `responseData.amounts.approved` of `2.57`, and `responseData.amounts.balanceDue` for the rest. The card doesn't select the table; the control field does. It's listed here because a partial approval is the outcome an integration has to handle on a prepaid card, and this is the card whose classification says why.
- **Card acceptance policy.** Prepaid sits on top of the debit rail, so the policy judges this card by **Accept prepaid cards** and by **Accept debit cards**, and refuses it when either is off. The refusal is the same HTTP 400 with `Transactions:CardNotAcceptedByMerchantPolicy` and a `reason` of `FundingSource`.
- **Surcharge.** On an actively surcharging merchant, `surchargeResult.ineligibilityReason` reads `PrepaidCard`. Prepaid cards are never surcharged.
- **Convenience fee.** Assessed as configured. The merchant's prepaid exemption toggle is stored and not applied at runtime, the same as the debit one.

## Visa commercial

Number `4005562231212123`, expiry `12/2030`, CVV `123`. Classifies as a commercial purchasing card.

```bash
curl -X POST "https://your-gateway-host/api/transactions" \
  -H "api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "transactionType": "Sale",
    "idempotencyKey": "test-cards-visa-commercial-0001",
    "cardData": {
      "cardNumber": "4005562231212123",
      "nameOnCard": "Acme Purchasing",
      "expirationMonth": 12,
      "expirationYear": 2030,
      "cvv": 123
    },
    "invoiceData": {
      "amounts": { "base": 10.00, "tax": 0.80, "total": 10.80 }
    },
    "level2Data": {
      "poNumber": "PO-10042"
    }
  }'
```

Expect `cardData.binData` with `brand` of `Visa`, `type` of `Credit`, `category` of `purchasing`, `issuedEntity` of `Commercial`, `fundingSource` of `Credit`, and `flags.commercial` of `true`. On `responseData`, `processorResponseDetail.isCommercialCard` is `true`. The trace entry's `catalogId` is `visa-commercial`.

What the card lets you observe:

- **The commercial-card indicator.** `isCommercialCard` on the processor response detail is derived from the stored BIN data, so it reads `true` for this card and `false` for every personal card above. Branch on it the way you would on a live processor's response.
- **Enhanced data enforcement.** When enhanced-data qualification is enabled for the deployment and the merchant's **Enhanced Data Enforcement** is **Warn** or **Strict**, the platform scores the Level 2 and Level 3 data on every commercial-card sale before it's created. The request above passes: it carries a purchase order number and a tax amount inside the accepted share of the total. Remove `level2Data` and set `tax` to `0.00`, and the assessment reports two findings, `MissingCustomerCode` and `ZeroTaxWithoutExemptFlag`. Under **Warn** the sale is still created and the response carries the findings on `enhancedDataQualification` with `isPreview` of `true`. Under **Strict** the sale is refused with HTTP 400 and a validation error for each finding carrying the code `Transactions:EnhancedDataRequirementsNotMet`. A personal card is never assessed, whatever the setting.
- **Card acceptance policy.** On a merchant with **Accept commercial cards** turned off, the request is refused with `Transactions:CardNotAcceptedByMerchantPolicy` and a `reason` of `IssuedEntity`.

## American Express commercial

Number `378730000000006`, expiry `12/2030`, CVV `1234`. Classifies as a commercial corporate card on the American Express rail. The verification value is four digits, as it's on every American Express card. The API accepts a three- or four-digit value on any brand, but the hosted and in-app card-entry surfaces size the CVV field by brand, so this is the card that shows whether your own entry surface does too.

```bash
curl -X POST "https://your-gateway-host/api/transactions" \
  -H "api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "transactionType": "Sale",
    "idempotencyKey": "test-cards-amex-commercial-0001",
    "cardData": {
      "cardNumber": "378730000000006",
      "nameOnCard": "Acme Purchasing",
      "expirationMonth": 12,
      "expirationYear": 2030,
      "cvv": 1234
    },
    "invoiceData": {
      "amounts": { "base": 10.00, "total": 10.00 }
    }
  }'
```

Expect `cardData.binData` with `brand` of `Amex`, `type` of `Credit`, `category` of `corporate`, `issuedEntity` of `Commercial`, `flags.commercial` of `true`, and `flags.regulated` of `true`, which is how the source certification data classifies this number. On `responseData`, `cardIndicator` reads `Amex` and `processorResponseDetail.isCommercialCard` is `true`. The trace entry's `catalogId` is `amex-commercial`.

What the card lets you observe:

- **Enhanced data on a brand with no published bounds.** The request above sends no purchase order and no tax, so under **Warn** or **Strict** the assessment reports the same two findings the Visa commercial card reports. The difference is what **Strict** does with them: nothing. Refusal is narrowed to Visa and Mastercard, the two brands whose data-rate programs publish bounds to score against, so an American Express sale is assessed and reported, never refused. If your integration reads the findings to decide whether to prompt for more data, this card is the one that proves the read doesn't depend on a refusal.
- **Card acceptance policy.** Refused under **Accept commercial cards** off, with a `reason` of `IssuedEntity`, the same as the Visa commercial card.
- **The four-digit CVV.** The sandbox's CVV-keyed verification triggers apply to this card with four-digit values. A client that formats or validates the CVV as exactly three digits can't send this card's value at all, which is the defect the card exists to surface.

## Visa government purchase (GSA)

Number `4486000000000005`, expiry `12/2030`, CVV `123`. Classifies as a government purchase card, which is also a commercial card.

No processor publishes a government test card, so this number is synthesized in the government purchase-card range. It resolves only through the sandbox classification and never from the BIN database, which makes it the one card in this guide that a merchant in **Processor Test** mode can't use: a real processor test host doesn't know it.

```bash
curl -X POST "https://your-gateway-host/api/transactions" \
  -H "api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "transactionType": "Sale",
    "idempotencyKey": "test-cards-visa-gsa-0001",
    "cardData": {
      "cardNumber": "4486000000000005",
      "nameOnCard": "Agency Cardholder",
      "expirationMonth": 12,
      "expirationYear": 2030,
      "cvv": 123
    },
    "invoiceData": {
      "isTaxExempt": true,
      "amounts": { "base": 10.00, "total": 10.00 }
    },
    "level2Data": {
      "poNumber": "PO-10043"
    }
  }'
```

Expect `cardData.binData` with `brand` of `Visa`, `type` of `Credit`, `category` of `purchasing government`, `issuedEntity` of `Commercial`, `flags.commercial` of `true`, and `flags.government` of `true`. On `responseData`, `processorResponseDetail.isCommercialCard` is `true`. The trace entry's `catalogId` is `visa-gsa`.

What the card lets you observe:

- **Where the government flag is visible.** On the API it's `cardData.binData.flags.government`. In the back office, the transaction detail shows the card as issued to a commercial entity and doesn't draw a separate government chip, so the flag is read from the API response or from the platform administrator's BIN lookup page, where it shows as **GSA**. Don't look for it on the transaction detail.
- **Enhanced data expectations.** Government purchase cards are commercial cards, so the enhanced-data assessment engages under **Warn** and **Strict** exactly as it does for the Visa commercial card. The request above passes because it sends a purchase order and declares the sale tax exempt; a government purchase is tax exempt, and `isTaxExempt` is what keeps a zero tax amount from being reported as a finding. Drop it to see `ZeroTaxWithoutExemptFlag`.
- **Card acceptance policy.** The policy judges this card by **Accept GSA cards** alone. Turning **Accept commercial cards** off doesn't refuse it, even though it's commercial; turning **Accept GSA cards** off does, with a `reason` of `IssuedEntity`.

## Visa healthcare (FSA)

Number `4373191234567806`, expiry `12/2030`, CVV `123`. Classifies as a healthcare card: prepaid debit with the healthcare flag.

```bash
curl -X POST "https://your-gateway-host/api/transactions" \
  -H "api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "transactionType": "Sale",
    "idempotencyKey": "test-cards-visa-fsa-0001",
    "cardData": {
      "cardNumber": "4373191234567806",
      "nameOnCard": "Jane Doe",
      "expirationMonth": 12,
      "expirationYear": 2030,
      "cvv": 123
    },
    "invoiceData": {
      "amounts": { "base": 45.00, "total": 45.00 }
    },
    "fsa": {
      "qhpAmount": 45.00,
      "rxAmount": 30.00
    }
  }'
```

Expect `cardData.binData` with `brand` of `Visa`, `type` of `Debit`, `category` of `prepaid healthcare`, `fundingSource` of `Prepaid`, `flags.healthcare` of `true`, and `flags.prepaid` of `true`. The trace entry's `catalogId` is `visa-fsa`.

What the card lets you observe:

- **The healthcare flag.** It's on `cardData.binData.flags.healthcare`, and the transaction detail in the back office draws it as an active **Healthcare** chip on the payment method panel. This is the flag healthcare-eligibility logic keys on, where a merchant has any.
- **What the platform does with a healthcare card today.** It accepts the `fsa` amounts (qualified health plan, prescription, vision, dental, clinical, copay, and transit) on the request, stores them on the transaction, and passes them to the processor as healthcare amounts. It doesn't validate the purchase against an inventory information approval system, doesn't check whether the merchant is registered for one, and doesn't refuse a non-healthcare purchase on a healthcare card. Send `fsa` amounts to exercise the fields; don't expect the platform to decide eligibility, because it doesn't.
- **Prepaid behavior.** Because the funding source is prepaid, this card is refused under **Accept prepaid cards** off (and under **Accept debit cards** off), and an actively surcharging merchant records `PrepaidCard` as the surcharge ineligibility reason.

## EBT

Number `5076800001111112`, expiry `12/2030`, CVV `123`. Classifies the way a real EBT card does: brand `EBT` on the debit rail. EBT isn't a card network, and the sandbox treats the card differently from every other one in this guide in two ways: the tender is never inferred from the card, and the amount triggers don't apply.

```bash
curl -X POST "https://your-gateway-host/api/transactions" \
  -H "api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "transactionType": "Sale",
    "idempotencyKey": "test-cards-ebt-0001",
    "cardData": {
      "cardNumber": "5076800001111112",
      "expirationMonth": 12,
      "expirationYear": 2030,
      "cvv": 123,
      "tenderType": "EbtSnap",
      "entryMode": "UnencryptedCardReaderSwipe",
      "pin": "ENCRYPTED_PIN_BLOCK",
      "keySerialNumber": "KSN_FROM_YOUR_PIN_PAD"
    },
    "invoiceData": {
      "amounts": { "base": 12.50, "total": 12.50 }
    },
    "customFields": [
      { "name": "loopback.availableBalance", "value": "87.50" }
    ]
  }'
```

Expect `cardData.binData` with `brand` of `EBT`, `type` of `Debit`, `category` of `standard`, and every flag `false`. On `responseData`, `cardIndicator` reads `EBT`, `detailedProductId` reads `Debit`, and `amounts.availableBalance` reads `87.50` from the control field. The trace entry's `catalogId` is `ebt`.

What the card lets you observe:

- **The tender has to be sent.** Send `tenderType` of `EbtSnap`, `EbtCash`, or `Ewic`. An EBT tender requires PIN data and a card-present entry mode: a request without `pin` is refused with `Transactions:TenderRequiresPin`, and one keyed with `entryMode` of `Manual` is refused with `Transactions:TenderRequiresCardPresent`. The sandbox doesn't read the PIN block or the key serial number, so the placeholders above are enough there; on a real processor they come from a PIN pad.
- **The merchant has to have the tender enabled.** The merchant's **Card tenders** settings (**EBT SNAP**, **EBT Cash**, **eWIC**) and a processor profile that lists the tender are what admit the sale to the rail. Send the request to a merchant without them and the routing decision on the request log shows the profile eliminated for `PaymentTypeNotSupported`; read that decision rather than the result code, because whether the gateway then refuses or falls back is a routing decision, not a card one.
- **What happens without the tender.** Send the same number with no `tenderType` and the sale processes as an ordinary debit card: no PIN is required, the amount triggers apply again, and the classification still reads brand `EBT`. That's the shape of the mistake this card exists to catch, a client that expects the card to pick the rail.
- **No amount trigger.** The sandbox approves an EBT sale at any amount unless a control field says otherwise, so an amount that declines a Visa sale approves here. Force an outcome with `loopback.resultCode`.
- **Balances and the eWIC discount.** The response amounts for benefit cards come from control fields: `loopback.beginningBalance` and `loopback.availableBalance` set the balances, and `loopback.ewicDiscount` sets the eWIC discount on an `Ewic` sale. Send them to see the amounts your integration displays after a benefit purchase.

## Reading the result

Two surfaces show you what the sandbox saw, and they agree with each other by construction because both read the same stored classification.

**The sandbox simulation panel.** Open the transaction in the back office and find the **Sandbox Simulation** panel beside the routing decision. It lists every trigger the request fired, and a classification card adds a **Test card** row naming the card by its catalog id, with the card's own purpose beside it. The row carries the id and never the number. This is the same information as the `loopbackSimulation` field on the response, drawn for a person.

**The BIN lookup page.** A platform administrator can open **BIN Lookup** under **Developer** in the back office, paste the full number, and read the same classification the transaction stored: brand, card type, card category, the sandbox issuer, and the **Commercial**, **Regulated**, **Healthcare**, and **GSA** flags. Paste the whole number: a prefix resolves from the BIN database, and only the full number resolves through the sandbox overlay.

If the two disagree with the response you got, the request didn't carry the number you think it did. Check for a space or a separator in `cardNumber`; the overlay matches digits only.

## See also

- [Verifying sandbox test cards](/help/guides/verifying-sandbox-test-cards): the same eight cards run from the Virtual Terminal, with a checklist of what to verify on the transaction detail after each one.
- [Processor routing and failover](/help/guides/processor-routing-and-failover): the routing decision an EBT sale on a merchant without the tender shows you, and what the sandbox processor modes mean.
- [Understanding declines and rejections](/help/guides/understanding-declines-and-rejections): how to read the refusals the card acceptance policy and enhanced-data enforcement return.

## See also

- [All documentation](https://devportal-simpay-sbx.winkpg.io/llms.txt): the machine-readable index of every public page on this site.
