Stripe publishes a set of card numbers that only exist inside its test environment. Each one triggers a specific, documented outcome — an approval, a particular decline, a 3-D Secure challenge — so you can exercise every branch of your payment code without a bank being involved.

They are not generic dummy numbers. They are recognised by Stripe and by nothing else, which is exactly what makes them useful and exactly why numbers from anywhere else, including our generator, do not work with them.

This page is the deep version. For the same numbers alongside PayPal, Braintree, Adyen, Square, and Authorize.Net, the test card numbers reference is the cross-gateway summary, and the PayPal sandbox guide is the equivalent deep dive for PayPal and Braintree — which select outcomes by cardholder name and transaction amount rather than by card number.

Test mode and live mode

Stripe environments are selected by the API key, not by the request:

Test modeLive mode
Secret keysk_test_…sk_live_…
Publishable keypk_test_…pk_live_…
Test cardsRecognisedRejected
Real cardsRejectedCharged
Money movesNoYes

The Dashboard has a toggle that switches which set of data you are looking at; the API has no such toggle, only the key. This single fact explains most confused bug reports in this area — “the test card stopped working” is almost always a live key in the environment, and “my real card was declined in staging” is the same mistake in the other direction.

On key handling: a test key is lower-risk than a live key, not zero-risk. It can read your test data, create objects, and in some integrations reveal your business structure. Keep both out of the repository, out of client-side bundles, and out of CI logs. Stripe’s API keys documentation covers rotation and restricted keys, which are worth using for anything running unattended.

Success cards by brand

Any future expiry and any CVC of the correct length work with all of these. The number alone determines the outcome.

Card numberBrandCVCPaymentMethod token
4242 4242 4242 4242Visa3 digitspm_card_visa
4000 0566 5566 5556Visa (debit)3 digitspm_card_visa_debit
5555 5555 5555 4444Mastercard3 digitspm_card_mastercard
2223 0031 2200 3222Mastercard (2-series)3 digits
5200 8282 8282 8210Mastercard (debit)3 digitspm_card_mastercard_debit
5105 1051 0510 5100Mastercard (prepaid)3 digitspm_card_mastercard_prepaid
3782 822463 10005American Express4 digitspm_card_amex
6011 1111 1111 1117Discover3 digitspm_card_discover
3056 9300 0902 0004Diners Club3 digitspm_card_diners
3566 0020 2036 0505JCB3 digitspm_card_jcb
6200 0000 0000 0005UnionPay3 digitspm_card_unionpay

The 2-series Mastercard is the one to keep in your fixtures deliberately. Brand detection written before 2017 classifies it as unknown, and it is the single most common card-type bug still shipping — the BIN and IIN guide explains why that range exists.

The right-hand column matters for server-side tests. Raw card numbers in your backend code put that code in PCI scope; the pm_card_* tokens produce the same results without a PAN ever touching your server, so prefer them anywhere you are not specifically testing the input form.

Decline cards

This is the section worth copying into a fixture file. Each number produces a real error object, not a simulated one.

Card numberError codeDecline code
4000 0000 0000 0002card_declinedgeneric_decline
4000 0000 0000 9995card_declinedinsufficient_funds
4000 0000 0000 9987card_declinedlost_card
4000 0000 0000 9979card_declinedstolen_card
4000 0000 0000 6975card_declinedcard_velocity_exceeded
4000 0000 0000 0069expired_card
4000 0000 0000 0127incorrect_cvc
4000 0000 0000 0119processing_error
4242 4242 4242 4241incorrect_number

That last number deliberately fails the Luhn checksum. It exists to test the branch where your own validation should have rejected the input before Stripe ever saw it — if it reaches the API, your client-side check has a gap.

Never show a decline code to the customer

lost_card and stolen_card are the reason this deserves its own heading.

Those codes are information from the issuer to the merchant. Displayed to the person at the checkout, they are either wrong — plenty of legitimate cards decline for reasons the code describes badly — or actively harmful, because telling someone holding a card that it is reported stolen is a safety problem, and telling a fraudster the same thing is a free diagnostic.

Map every decline to one of two customer-facing messages: try a different payment method, or contact your bank. Log the real code for yourself. Stripe’s decline codes reference documents which are retriable and which are not — that distinction belongs in your retry logic, not in your copy.

3-D Secure test cards

Card numberBehaviour
4000 0000 0000 3220Always requires a 3DS2 challenge, then succeeds
4000 0027 6000 3184Requires authentication on all transactions
4000 0084 0000 1629Requires authentication, then declines afterwards
4000 0025 0000 3155Requires authentication unless set up for off-session use
4000 0000 0000 3055Supports authentication but does not require it
4242 4242 4242 4242Supports 3DS but is not enrolled — no challenge appears
3782 822463 10005No 3DS support at all

4000 0084 0000 1629 is the important one. Code that treats a completed challenge as a completed payment breaks here, and that assumption is common enough that it is worth an explicit test. Authentication proves who the cardholder is; it does not commit the issuer to approving anything. The 3-D Secure testing guide covers the flow in full.

Testing individual fields

  • Expiry — any future month and year. To exercise your own expiry validation, a past date is rejected client-side before Stripe is involved; to see the API’s expired_card error, use 4000 0000 0000 0069 with a future date.
  • CVC — any three digits, four on American Express. Omitting it entirely makes Stripe skip the check, which means a CVC test that passes with no CVC sent is not testing anything.
  • Postal code and AVS — any value succeeds by default; Stripe documents specific cards for triggering address and postal-code check failures. As with CVC, omitted values are skipped rather than failed.
  • Cardholder name — free text on Stripe, unlike PayPal’s sandbox where the name selects the outcome.

Beyond cards

Card testing is not the whole surface. Stripe documents separate test values for SEPA Direct Debit, iDEAL, Bancontact, ACH direct debit, and the rest of its payment method catalogue — each with its own success and failure identifiers. If your checkout offers anything other than cards, those paths need the same treatment; the testing documentation has a method selector at the top of the page for exactly this.

Testing webhooks

Webhooks are where integrations most often break in production while passing every test, because the local development loop skips them entirely.

The Stripe CLI closes that gap. It forwards real test-mode events to a local port and prints a signing secret scoped to that session:

stripe listen --forward-to localhost:3000/webhook
# → Ready! Your webhook signing secret is whsec_... (^C to quit)

# In another terminal, fire a specific event on demand:
stripe trigger payment_intent.succeeded

Verify the signature on every request. The raw request body is required — parsed JSON will not verify, which is the most common cause of a handler that works with stripe trigger and fails against real deliveries:

import Stripe from 'stripe';
import express from 'express';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
const app = express();

app.post(
  '/webhook',
  express.raw({ type: 'application/json' }), // raw body, not express.json()
  (req, res) => {
    let event;
    try {
      event = stripe.webhooks.constructEvent(
        req.body,
        req.headers['stripe-signature'],
        process.env.STRIPE_WEBHOOK_SECRET
      );
    } catch (err) {
      return res.status(400).send(`Webhook signature failed: ${err.message}`);
    }

    // Stripe retries on non-2xx and can deliver the same event twice.
    // Record event.id and return early if you have already handled it.
    if (alreadyHandled(event.id)) return res.json({ received: true });

    if (event.type === 'payment_intent.succeeded') {
      fulfil(event.data.object);
    }

    markHandled(event.id);
    res.json({ received: true });
  }
);

Two properties to test explicitly: a tampered signature must be rejected, and the same event delivered twice must fulfil the order once. Stripe’s webhook documentation sets out the delivery and retry guarantees you are coding against.

Test clocks for subscriptions

Subscription bugs live in the future — a renewal that fails, a trial that converts, a dunning sequence that never fires. Waiting a month to find out is not a test strategy.

Test clocks let you attach a customer to a simulated clock and advance it. A year of billing cycles runs in a few seconds, with real invoices, real webhook events, and real failure behaviour. Combine one with 4000 0000 0000 9995 on the second renewal and you can watch your dunning flow work before a customer ever meets it. It is the least-used feature in this list and the one that finds the most bugs.

Common mistakes

  1. Test card against a live key. The card is fine; the key is wrong. Check which key the environment actually loaded before debugging anything else.
  2. Sending a generated number to Stripe. It passes your form and dies at the API, because no issuer stands behind it — why test cards fail on real systems covers the mechanism.
  3. Only testing the happy path. The decline table exists so that your error handling is exercised. A checkout tested only with 4242… has never run its own failure branch.
  4. Not testing webhooks. Your fulfilment logic almost certainly lives there. Skipping it means the least-tested code owns the most important step.
  5. Trusting the client-side confirmation. Fulfil on payment_intent.succeeded from a verified webhook, not on the browser reporting success — the browser can close, lie, or be replayed.
  6. Never testing 3DS. Authentication is mandatory in the EEA and increasingly common elsewhere. An untested challenge flow is an untested checkout.

For the input layer that sits in front of all of this — field lengths, brand detection, masking, and bulk fixtures from the bulk generatorthe payment form testing checklist covers what to assert before a request ever reaches Stripe.

The card numbers, error codes and CLI commands above were last checked against provider documentation on . Providers do change what they publish — the official link beside each claim is authoritative.

Frequently Asked Questions

4242 4242 4242 4242, a Visa that succeeds. Pair it with any future expiry date and any three-digit CVC — the number alone decides the outcome, so there is no combination to memorise. It is the most widely recognised test number in payments, and it works only inside Stripe’s test mode.
No, and that is deliberate. Test numbers are recognised only by test-mode API keys; send one to a live key and Stripe rejects it outright. The reverse is also true — a real card used against a test key does nothing. The environment is selected by the key, not by the card, which is why a test card ‘failing’ in production almost always means the wrong key is loaded.
Use the card documented for the decline you want. 4000 0000 0000 0002 returns a generic decline, 4000 0000 0000 9995 returns insufficient funds, 4000 0000 0000 0069 returns an expired card, and 4000 0000 0000 0127 returns an incorrect CVC. Each one produces the real error object your code will see in production, which is the point — testing only the success path leaves your error handling unexercised.
4000 0000 0000 3220 always requires a 3DS2 challenge and succeeds once authenticated. For the case that breaks naive code, use 4000 0084 0000 1629: it requires authentication and then declines anyway, proving that a completed challenge is not an approved payment.
Only for your own form. A generated number passes Luhn and brand detection in the browser, so it exercises your input handling correctly, but Stripe’s API rejects it the moment it arrives because it is not in the test-card set and there is no issuer behind it. Use generated numbers to test your form and Stripe’s numbers to test Stripe.
With the Stripe CLI. Run stripe listen –forward-to localhost:3000/webhook and it forwards live test-mode events to your machine, printing a signing secret to use for signature verification. stripe trigger payment_intent.succeeded fires a specific event on demand. Verify the signature on every request and make your handler idempotent — Stripe retries, so the same event will arrive twice.