Collecting Cards
Topics covered on this page
๐ณ Collecting cards with Omise.js
This article will help you build a form that lets you collect cards directly from a page on your website and tokenize them.
Omise.js allows you to collect card information easily. Omise.js is a client-side JavaScript library that lets you run your own HTML form in your customer's browser. It sends sensitive card data to the Omise server and receives a card token in exchange. Forward that token to your server for processing โ your server never has to handle sensitive card information directly.
๐ The only supported way to send card data to Omise is via JavaScript using Omise.js, unless your organization holds a PCI-DSS license permitting server-side card data handling.
โ ๏ธ Requirement: Your checkout page must be served over HTTPS. Omise.js will not function on pages served over plain HTTP. We recommend enabling HTTPS across your entire site, not only the checkout page. (Source: docs.omise.co/omise-js)

โ๏ธ How it works
On a high level, this is how it works:
- Using Omise.js and your public key, send the cardholder data from your customer's browser to Omise.
- The Omise tokens service responds with a single-use card token.
- Forward the token back to your server.
- Use the token to take an action on the card. You can charge the card, save the card to a new customer, or attach the card to an existing one.
๐ก We recommend against storing the token. Since it is for one-time use only, there is no benefit in saving it for later โ use it and discard it immediately.
๐งช Try it: Omise Token Simulator
๐ Publishing note: pkey_test_XXXXXXXXXXXXXXXXXXXX is a sanitized placeholder for this document. A real publishable test public key must be substituted back in so the simulator above will function live โ public keys are non-secret by design and safe to expose client-side. Confirm which test key is approved for use in published docs before restoring it.
You can learn more about the tokens API in the tokens reference.
๐ป A full-fledged example
First, insert Omise.js into your webpage. Add it before the closing </body> tag.
<script src="https://cdn.omise.co/omise.js"></script>
The Omise.js library does not require jQuery, but this example uses it as a convenient way to access the DOM. If you prefer not to add the jQuery dependency, the same form-submit and DOM-lookup logic can be written in vanilla JavaScript.
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
Then add your public key so Omise.js can authenticate against the Omise API:
<script>
Omise.setPublicKey("pkey_test_XXXXXXXXXXXXXXXXXXXX");
</script>
Next, build a form to collect the card details.
<form action="/checkout" method="post" id="checkout">
<div id="token_errors"></div>
<input type="hidden" name="omise_token">
<div>
Name<br>
<input type="text" data-omise="holder_name">
</div>
<div>
Number<br>
<input type="text" data-omise="number">
</div>
<div>
Date<br>
<input type="text" data-omise="expiration_month" size="4"> /
<input type="text" data-omise="expiration_year" size="8">
</div>
<div>
Security Code<br>
<input type="text" data-omise="security_code" size="8">
</div>
<input type="submit" id="create_token">
</form>
Next, trigger token creation when the submit button is pressed. On success, populate the token field and clear the sensitive fields so they aren't submitted to your server.
$("#checkout").submit(function () {
var form = $(this);
// Disable the submit button to avoid repeated clicks.
form.find("input[type=submit]").prop("disabled", true);
// Serialize the form fields into a valid card object.
var card = {
"name": form.find("[data-omise=holder_name]").val(),
"number": form.find("[data-omise=number]").val(),
"expiration_month": form.find("[data-omise=expiration_month]").val(),
"expiration_year": form.find("[data-omise=expiration_year]").val(),
"security_code": form.find("[data-omise=security_code]").val()
};
// Send a request to create a token, then trigger the callback function once
// a response is received from Omise.
//
// Note that the response could be an error, and this needs to be handled within
// the callback.
Omise.createToken("card", card, function (statusCode, response) {
if (response.object == "error" || !response.card.security_code_check) {
// Display an error message.
var message_text = "SET YOUR SECURITY CODE CHECK FAILED MESSAGE";
if (response.object == "error") {
message_text = response.message;
}
$("#token_errors").html(message_text);
// Re-enable the submit button.
form.find("input[type=submit]").prop("disabled", false);
} else {
// Fill in the omise_token field.
form.find("[name=omise_token]").val(response.id);
// Remove card number and security code from the form before submitting to server.
form.find("[data-omise=number]").val("");
form.find("[data-omise=security_code]").val("");
// Submit the token to the server.
form.get(0).submit();
}
});
// Prevent the form from being submitted with raw card data.
return false;
});
That's it. Omise.js collects the credit card information and returns a token, which you can use to take action on the card.
โน๏ธ Note: The examples above include !response.card.security_code_check as part of the error check. As of April 1, 2020, Omise permanently hardcodes security_code_check to always return true in Token and Card API responses โ this was a deliberate fix to close a CVV brute-forcing vector. As a result, that portion of the condition can no longer evaluate to true and has had no effect since the change. Error handling should rely on response.object == "error" (or statusCode !== 200) alone; the card's actual validity is determined when the token is used to create a charge, not at token-creation time. (Source: docs.omise.co/protecting-you-against-fraudsters)
โ FAQ
Does my server ever see raw card data? No. Card data is sent from the customer's browser directly to Omise via Omise.js. Your server only ever receives the resulting token, never the card number, expiration date, or security code.
Should I store the token for later use? No. Tokens are single-use โ once you've used a token to charge a card, save it to a customer, or attach it to an existing customer, it has served its purpose. Use it immediately and discard it; there's no benefit to storing it.
Is it safe to expose my public key (pkey_test_... / pkey_...) in client-side JavaScript?
Generally yes, but safe to expose
isn't the same as risk-free.
Public keys are designed for client-side use โ they're scope-limited to creating tokens and sources, and cannot create charges, move funds, or perform any action that requires your secret key. However, Omise has documented a real attack that specifically relied on an exposed public key: combined with a stolen card number, a public key could previously be used to brute-force a card's CVV by submitting repeated token-creation attempts and reading the security_code_check field in each response. Omise closed this on April 1, 2020 by permanently hardcoding security_code_check to always return true. So: the key itself can't move money, but an exposed public key paired with stolen card data can still be abused for card-testing/enumeration attacks โ which is part of why Omise layers pre-authorization, IP geolocation, and behavioral fraud analysis on top of key scoping. (Source: docs.omise.co/protecting-you-against-fraudsters, docs.omise.co/api-authentication)
Do I need jQuery to use Omise.js? No. jQuery is used in these examples purely for convenience when accessing the DOM and handling the form submit event. Omise.js itself has no jQuery dependency, and the same logic can be written in vanilla JavaScript.
How long is an unused token valid before it expires? Tokens are valid for a single use and expire after a short period if unused โ typically a few minutes. Always use a token promptly after receiving it; do not cache or hold onto it. (Source: docs.omise.co/omise-js)
What happens if I try to reuse a token that's already been used?
The API returns a used_token error with the message token was already used.
Tokens are single-use only โ once a token has been used to create a charge or attach a card, it cannot be used again, regardless of whether that charge succeeded. (Source: docs.omise.co/api-errors)
Can I restrict my public key to specific domains, the way some other APIs let you lock down a client-side key? No. Omise doesn't offer domain-based key restriction. The public key's safety for client-side use comes from its limited scope โ it can only create and view tokens and sources, and cannot perform any action that requires a secret key. (Source: docs.omise.co/api-authentication)
Is there a rate limit on how many tokens I can create? Yes. Token creation on Omise's Vault (the tokenization endpoint) has a significantly lower rate limit than the main API. An exact numeric limit isn't published; if you're sending many requests in a short window, spread them out rather than sending them in parallel bursts, and contact support@omise.co ahead of a high-traffic event such as a sale. (Source: docs.omise.co/api-rate-limiting)
After I use the token to create a charge, do I need to handle 3D Secure separately?
Possibly. If 3D Secure is enabled on your account, the charge response can include an authorize_uri that you redirect the cardholder to for bank-side authentication before the charge completes. 3D Secure is mandatory for certain business types โ travel, digital goods, gaming, and other categories prone to fraud and chargebacks โ as determined by Omise's fraud analysts; for other merchants it's optional but recommended for high-value or high-risk transactions. As of October 2022, only 3D Secure 2 (3DS2) is supported โ 3DS1 was deprecated, so plan for the authorize_uri redirect and the frictionless/challenge flows it can trigger. (Source: docs.omise.co/3d-secure)