Use Your Own Card Inputs
Tokenize with your inputs for flexibility and secure tokenization.Despite Basis Theory Elements being highly customizable and providing the most secure and efficient solution for collecting cards in user-facing applications, we understand that some companies are experienced with PCI DSS and feel comfortable having their frontend, or parts of it, handle cardholder data and be in scope for such compliance.
In this guide, we will set up Basis Theory SDKs to receive cards from your frontend application and securely store the cardholder data as tokens with the Basis Theory Platform. This practice is well-positioned to substantially de-scope your servers, networking resources and database from PCI DSS compliance, while retaining full control of the sensitive data. If you want to learn more how Basis Theory can help you achieve that, reach out to our team!
Getting Started
To get started, you will need to create a Basis Theory Account and a TEST Tenant.
Public Application
You will need a Public Application for your frontend. Click here to create one using the Basis Theory Customer Portal.
This will create an application with the following Access Controls:
- Permissions:
token-intent:create
Configure the SDK
We will perform cardholder data tokenization by calling the Basis Theory API and passing the values from your inputs. The use of SDKs is optional, you can use your preferred HTTP Client in your frontend application.
For Javascript, React and React Native:
For Android:
For iOS, use your preferred HTTP client to call the Basis Theory API.
Tokenization
We won't get into details about particular frontend libraries or custom input implementations, but show how to call the API to store cards in your Basis Theory Tenant.
To do this, we will invoke the Create Token Intent endpoint from the frontend, passing the cardholder data in the payload. This will securely create a card
token intent by transferring the card information from your frontend to the Basis Theory API, where the data will be strongly encrypted and stored in a compliant environment.
Let's use a submit function to make the request, triggered from a button:
- JavaScript
- Android
- iOS
<button onclick="submit();">Submit</button>
import { BasisTheoryClient, BasisTheory } from "@basis-theory/node-sdk";
const client = new BasisTheoryClient({ apiKey: "<PUBLIC_API_KEY>" });
async function submit () {
try {
// get these values from your inputs
const intent = await client.tokenIntents.create({
type: "card",
data: {
number: '4242424242424242',
expiration_month: 12,
expiration_year: 2025,
cvc: '123'
},
});
// TODO post the intent object to your backend
} catch (error) {
console.error(error);
}
}
init();
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
...
<Button
android:id="@+id/submit_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:backgroundTint="#00A4BA"
android:text="Submit" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
import androidx.lifecycle.lifecycleScope
import com.basistheory.core.ClientOptions;
import com.basistheory.core.Environment
import com.basistheory.resources.tokenintents.TokenIntentsClient;
import com.basistheory.resources.tokenintents.requests.CreateTokenIntentRequest
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class MainActivity : AppCompatActivity() {
private val tokenClient by lazy {
TokenIntentsClient(
ClientOptions.builder()
.environment(Environment.DEFAULT)
.addHeader("BT-API-KEY", "<PUBLIC_API_KEY>")
.build()
)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button = findViewById(R.id.submit_button)
button.setOnClickListener {
lifecycleScope.launch {
submit()
}
}
}
private suspend fun submit() {
val intent = withContext(Dispatchers.IO) {
tokenClient.create(
CreateTokenIntentRequest.builder()
.type("card")
.data(mapOf(
// get these values from your inputs
"number" to "4242424242424242",
"expiration_month" to 12,
"expiration_year" to 2025,
"cvc" to "123"
))
.build()
)
}
// TODO post the intent object to your backend
}
}
Use your preferred HTTP client to call the Create Token Intent endpoint.
<API_KEY>
with the Public API Key you created in the Public Application step.The created Token Intent is a short-lived tokenized card
object which carries only non-sensitive information following the Token Intent Object specification:
{
"id": "d2cbc1b4-5c3a-45a3-9ee2-392a1c475ab4",
"type": "card",
"tenant_id": "4aee08b9-5557-474b-a120-252e01fc7b0f",
"fingerprint": "BKJYqf2tcvhTHSXN7EvBJLviN3PBYRgwoJgce8VAfnSr",
"card": {
"bin": "424242",
"last4": "4242",
"expiration_month": 10,
"expiration_year": 2028,
"brand": "visa",
"funding": "credit",
"authentication": "sca_required",
"issuer_country": {
"alpha2": "GB",
"name": "UNITED KINGDOM OF GREAT BRITAIN AND NORTHERN IRELAND",
"numeric": "826"
}
},
"created_by": "0bc89db5-fadd-4d57-af93-10472e35ebd3",
"created_at": "2025-03-10T14:23:56.5580574+00:00",
"expires_at": "2025-03-11T14:23:56.5580575+00:00"
}
Authentication
Once the Token Intent has been created, merchants may need to authenticate the cardholder to comply with PSD2’s Strong Customer Authentication (SCA) requirements and reduce fraud. The specific authentication steps depend on the card’s region, issuer requirements, and the merchant’s risk strategy.
3D Secure
3D Secure (3DS) is the standard protocol for meeting PSD2 SCA requirements in Europe and enhancing fraud prevention globally. It introduces an additional verification step, such as a one-time password (OTP) or biometric authentication. SCA exemptions may apply for certain transactions, such as low-risk or merchant-initiated payments. Implementing 3DS2 ensures compliance while keeping checkout friction minimal.
if (intent.card.authentication === 'sca_required') {
// trigger the 3DS authentication flow
}
Next Steps
Now that you are securely storing your users' sensitive card data with Basis Theory, the next step is to process payments using your newly tokenized card data. Check out the following guides to complete your integration:
- Verify a Card – Ensure the card is valid by performing a $0 auth.
- Charge a Card – Process a payment using the stored token.