Validate a credit card number with the Luhn checksum
This credit card Luhn validator removes ordinary spaces and hyphens from a supplied card number, checks that every remaining character is a digit, and applies the deterministic Luhn checksum.
Run — free
It also reports the likely card network from recognized Visa, Mastercard, American Express, and Discover prefixes. The result can catch common entry mistakes before a payment request, but it cannot prove that an account exists, is active, belongs to a customer, or can complete a purchase.
How normalization and input checking work
Enter the card number as a string, either as uninterrupted digits or in a familiar display format such as groups separated by spaces or hyphens. The validator removes only those two separators. It then requires every remaining character to be an ASCII digit. Letters, punctuation, slashes, underscores, and other symbols produce an invalid-input error instead of being silently discarded. This strict behavior matters because aggressive cleanup can transform an accidental or malformed value into a different number and make the result misleading. An empty string, a value made only from separators, or a non-string value is rejected as well. The returned object never echoes the normalized number; it contains only the checksum verdict and the likely network. Treat the original input as sensitive payment data throughout your own application, even though this calculation is local and requires no issuer lookup, payment authorization, network request, random value, or persistent state.
What the Luhn result means
The Luhn algorithm is a check-digit calculation designed to detect common transcription mistakes. Starting at the rightmost digit, the validator alternates between leaving a digit unchanged and doubling it. Any doubled value above nine is reduced by nine, all resulting values are added, and the number passes when the total is divisible by ten. A passing result means only that the supplied sequence is mathematically consistent with its final check digit. It does not show that a bank issued the number, that the account remains open, that funds are available, or that the person entering it is authorized to use it. A fabricated sequence can pass Luhn, while a genuine card entered with one wrong digit usually fails. Use the result as early form feedback or a data-quality check, then rely on a compliant payment processor for tokenization, authentication, authorization, fraud controls, and the final decision about whether a transaction may proceed.
How likely network identification works
Network identification is inferred from the issuer identification prefix, not discovered through a remote registry. A number beginning with 4 is reported as Visa. Mastercard recognition includes the traditional 51 through 55 range and the newer 2221 through 2720 range. American Express uses prefixes 34 and 37. Discover recognition covers 6011, 65, 644 through 649, and the allocated 622126 through 622925 range. If none of these rules matches, the network is returned as unknown while the Luhn verdict is still calculated normally. The word likely is important: prefix allocations evolve, co-branded products exist, and this tool deliberately recognizes only the four requested networks. Network detection and checksum validation are independent, so a number can have a recognized prefix yet fail Luhn, or pass Luhn while its prefix remains unknown. Each request uses the published base price of $0.002; there is no variable charge based on number length or detected network.
What you can do with it
Checkout entry feedback
Catch a likely mistyped card digit before handing the payment details to a compliant processor for authorization.
Imported record quality checks
Test whether legacy card-number strings are structurally well formed without claiming that the underlying accounts are active.
Payment form testing
Verify that formatting separators are accepted and malformed characters are rejected consistently in a test workflow.
FAQ
Does passing the Luhn check prove that a card is real?
No. It proves only that the digits satisfy a checksum. Existence, ownership, status, funds, and authorization require a payment processor and issuer response.
Which formatting characters can I include?
You may include spaces and hyphens. They are removed before validation; any other non-digit character causes an invalid-input error.
Which card networks can be identified?
The prefix rules identify likely Visa, Mastercard, American Express, and Discover numbers. Other prefixes return unknown.
Can a recognized network prefix still have an invalid checksum?
Yes. Prefix classification and the Luhn calculation are independent, so a Visa-like prefix does not guarantee a passing checksum.
Does the validator contact a bank or card network?
No. The result comes from deterministic arithmetic and prefix rules, with no network lookup or authorization attempt.
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/data/credit-card-luhn-validate \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"number":"4111 1111 1111 1111"}'const res = await fetch("https://api.kit.forhosting.com/data/credit-card-luhn-validate", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"number": "4111 1111 1111 1111"
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/data/credit-card-luhn-validate",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"number": "4111 1111 1111 1111"
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/data/credit-card-luhn-validate", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"number":"4111 1111 1111 1111"}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"number":"4111 1111 1111 1111"}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/data/credit-card-luhn-validate", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"number": "4111 1111 1111 1111"
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "data.credit_card_luhn_validate",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Limits
max_mb | 25 |
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |