Single Card Enrollment — API Reference
Introduction
Customers with personal cards or business cards can add their cards individually. Cardholders are required to complete a verification process to ensure they are the owner of the card when they link it.
Enrollment is done with Astrada's pre-built Card Enrollment SDK — see Get the SDK and Installation. This page is the API reference the SDK implements; the endpoint-level detail is for understanding the flow (and for approved partners with direct-API access).
This page walks you through the process and steps that cardholders will take when linking cards through a Single Card Enrollment experience.
You do not make these API calls yourselfThe SDK performs every call on this page for you. A standard integration is just: embed the SDK → handle
onSuccess/onError. The walkthrough below documents what the SDK does under the hood — read it to understand the flow; only approved partners with direct-API access implement it themselves.
The enrollment flow (under the hood)
1. Customer Introduction
The first stage we recommend in any card enrollment journey is to clearly outline to your customers the scope of the data sharing they are about to consent to and any important information about how their data will be used.
This step improves user conversion by providing a feeling of trust and security.
2. Card Data Collection
The second stage of the card linking journey requires the collection of card data and consent. It is at this stage that a user provides you with their sensitive data and opts into the terms of the data sharing arrangement explicitly.
API Request
POST https://api.astrada.co/card-subscriptions{
"subaccountId": "<SUBACCOUNT_ID>",
"country": "<ISSUING COUNTRY>",
"expiryMonth": "<EXP_MONTH>",
"expiryYear": "<EXP_YEAR>",
"pan": "<CARD_NUMBER>",
"cvc": "<CVC_NUMBER>",
"cardholderName": "<HOLDER NAME>"
}This call starts the card enrollment process onto a specific Subaccount — the SDK makes it when the cardholder submits the card form. A successful create returns 201 Created.
409 Conflict— subscription already exists. If a subscription for the same card already exists on this subaccount,POST /card-subscriptionsreturns409withdetail: "There is already a card subscription for the specified card". Treat this as a non-fatal "already enrolled" signal — the SDK does this by issuing aGETand continuing with the existing subscription's state.
3. Start card verification
Card verification steps:
As mentioned here, this is the expected 3DS card verification flow:
- Device fingerprint is collected;
- Device fingerprint is transmitted to the issuer's Access Control System (ACS)
- Depending on issuer response a (3DS) challenge is required
- If the fingerprint is determined to be low risk, the challenge step is skipped. (Risk Assessment factors here)
- If the fingerprint is approved but higher risk, the challenge is initiated
- If the fingerprint fails, it is usually due to a lost/stolen card, an inactive card, or a card that is otherwise unable to transact.
Every card subscription created through Single Card Enrollment is subject to cardholder verification, so the SDK immediately follows up with the call to start it:
Choosing how strict verification isNew to enrollment? Start with Quick Start: Card Enrollment for the end-to-end flow. The verification experience above depends on the subaccount's risk tier. See Verification Risk Tiers to pick a tier, HIGHEST Verification for the strictest two-hold flow, Sandbox Testing & Test Cards to test each tier, and Error States & Remediation for every failure path.
API Request
POST https://api.astrada.co/card-verifications/3ds{
"subaccountId": "<SUBACCOUNT_ID>",
"country": "<ISSUING COUNTRY>",
"expiryMonth": "<EXP_MONTH>",
"expiryYear": "<EXP_YEAR>",
"pan": "<CARD_NUMBER>",
"cvc": "<CVC_NUMBER>",
"cardholderName": "<HOLDER NAME>"
}A successful call returns a Card Verification ID (id), used throughout the subsequent steps to track the current card verification step. A successful create returns 201 Created.
Starting card verification on a previously created subscription uses this same endpoint.
If at any given point in time you want to check the card verification status on a particular subscription, you can either make the GET Card Verification request, or a List a Card Verification steps (below)
GET https://api.astrada.co/card-verifications/3ds/{verificationId}GET https://api.astrada.co/card-verifications/3ds/{verificationId}/stepsIn most successful cases, the response for this request will direct the card holder verification to perform device fingerprint. This should be evident through the currentStep property in the response, with a link to the fingerprint step. If this is the case, follow steps outlined on step 3.1 below.
{
"id": "9fab1bea-bc1e-4757-bd47-479422e5983b",
"subaccountId": "f297d659-c13d-4219-aeaa-e10a845140a5",
"cardId": "8309b5f8-d5d8-49bb-9001-38bf1bb0f1e4",
"type": "3DS",
"currentStepId": "fingerprint",
"state": "in-progress",
"createdAt": "2024-01-04T18:53:32.000Z",
"updatedAt": "2023-01-04T10:55:12.000Z",
"_links": {
"self": {
"href": "/card-verifications/3ds/9fab1bea-bc1e-4757-bd47-479422e5983b"
},
"steps": {
"href": "/card-verifications/3ds/9fab1bea-bc1e-4757-bd47-479422e5983b/steps"
},
"currentStep": {
"href": "/card-verifications/3ds/9fab1bea-bc1e-4757-bd47-479422e5983b/steps/fingerprint"
}
}
}3.1 Device Fingerprint
As mentioned here, the card verification flow follows a 1 or 2 step process, depending on the risk appreciation of the 3DS provider (Issuer).
The first step is to attempt a frictionless verification flow by collecting device and browser data to infer identity verification, known as Device Fingerprinting. The SDK triggers this with the following API call:
GET https://api.astrada.co/card-verifications/3ds/{verificationId}/steps/fingerprintUse the Card Verification ID (
id) mentioned on step 3 above
{
"id": "fingerprint",
"subaccountId": "f297d659-c13d-4219-aeaa-e10a845140a5",
"verificationId": "9fab1bea-bc1e-4757-bd47-479422e5983b",
"type": "fingerprint",
"state": "in-progress",
"data": {
"threeDSMethodData": "eyJ0aHJlZURTTWV0aG9kTm90aWZpY2F0aW9uVVJMIjoiaHR0cHM6Ly9zYW5kYm94LmFwaS5tYXN0ZXJjYXJkLmNvbS9vcGVuYXBpcy9hdXRoZW50aWNhdGlvbi9jYWxsYmFja3MvdGhyZWVEU01ldGhvZE5vdGlmaWNhdGlvbiIsInRocmVlRFNTZXJ2ZXJUcmFuc0lEIjoiOTNhN2NjNzUtY2I3Yy00Y2QzLWEwNTMtYjJjNGMxODZiZTVmIn0=",
"threeDsMethodUrl": "https://acs-public.tp.mastercard.com/api/v1/3ds_method",
"threeDSMethodNotificationURL": "https://sandbox.api.mastercard.com/openapis/authentication/callbacks/threeDSMethodNotification",
"threeDSServerTransID": "93a7cc75-cb7c-4cd3-a053-b2c4c186be5f"
},
"createdAt": "2024-01-05T14:46:50.000Z",
"updatedAt": "2024-01-05T14:46:51.000Z",
"_links": {
"self": {
"href": "/card-verifications/9fab1bea-bc1e-4757-bd47-479422e5983b/3ds/steps/fingerprint"
},
"nextStep": {
"href": "/card-verifications/9fab1bea-bc1e-4757-bd47-479422e5983b/3ds/steps/challenge"
}
}
}The SDK uses the data properties to open a hidden iframe and collect the user's browser information. This hidden iframe POSTS to ACS (Access Control System) and the response is a page that collects the device information and sends it to ACS. The fingerprint iframe posts a message to the window (threeDSMethodNotificationURL) when it is complete.
Under the hood it runs HTML like the snippet below:
<html>
<head>
<script src="/static/fingerprint.js"></script>
<script>
window.onload = function() {
doFingerprint(
'{{ threeDsMethodUrl }}',
'{{ threeDSMethodNotificationURL }}',
'{{ threeDSMethodData }}',
'{{ threeDSServerTransID }}');
}
</script>
</head>
</html>On load calls the doFingerprint javascript function as such:
// This listener receives events from the window.
// On the arrival of threeds-method-notification event, it forwards
// the required data to the server to start authentication.
function fingerprintCompleteListener(m) {
if (m.data.type === 'threeds-method-notification') {
console.log('fingerprintCompleteListener called');
proceedAfterFingerprint('complete');
}
};
// Next step after fingerprinting (either when it is completed or it was not needed)
function proceedAfterFingerprint(fingerprintStatus) {
// Below mentioned parameters controls the looks of ACS challenge window. Values differs as per device configuration.
// For simplicity we have hardcoded some of them.
const body = {
fingerprintStatus: fingerprintStatus,
browserData: {
challengeWindowSize: '04', // 600x400
acceptHeader: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
colorDepth: window.screen.colorDepth,
javaEnabled: true,
language: navigator.language,
screenHeight: window.screen.height,
screenWidth: window.screen.width,
timezone: new Date().getTimezoneOffset(),
userAgent: window.navigator.userAgent,
},
};
post("https://api.astrada.co/card-verifications/3ds/{verification-id}/steps/fingerprint", body);
};
function doFingerprint(threeDsMethodUrl, threeDSMethodNotificationURL, threeDSMethodData, threeDSServerTransID) {
if (threeDsMethodUrl) {
const html = `<script>
document.addEventListener("DOMContentLoaded", function () {
var form = document.createElement("form");
form.method = "POST";
form.action = "${threeDsMethodUrl}";
form.appendChild(createInput("threeDSMethodNotificationURL", "${threeDSMethodNotificationURL}"));
form.appendChild(createInput("threeDSMethodData", "${threeDSMethodData}"));
form.appendChild(createInput("threeDSServerTransID", "${threeDSServerTransID}"));
document.body.appendChild(form);
form.submit();
document.body.removeChild(form);
});
function createInput(name, value) {
var result = document.createElement("input");
result.name = name;
result.value = value;
return result;
}
</script>`
const iframe = document.createElement("iframe");
iframe.id = '3ds-fingerprint';
document.body.appendChild(iframe);
iframe.style.display = "none";
const win = iframe.contentWindow;
if (win != null) {
const doc = win.document;
win.name = "3DS Fingerprint";
doc.open();
doc.write(html);
doc.close();
}
window.addEventListener("message", fingerprintCompleteListener);
} else {
// No threeDsMethodUrl so skip fingerprinting
proceedAfterFingerprint('unavailable');
}
};Once fingerprinting is complete, the browser details are sent to the ACS so it can correctly size the challenge iframe that may be prompted next.
The
fingerprintStatusin the request body is the client-side status of the fingerprint iframe — one ofcomplete,timeout, orunavailable. Theoutcomein the response is a separate field the service returns to indicate what happens next; do not conflate the two.
That happens via the following request:
POST https://api.astrada.co/card-verifications/3ds/{verificationId}/steps/fingerprint{
"fingerprintStatus":"complete",
"browserData":{
"colorDepth":24,
"language":"en-US",
"timezone":-120,
"screenHeight":1080,
"screenWidth":1920,
"challengeWindowSize":"05",
"javaEnabled":false,
"acceptHeader":"application/json",
"userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
"ip":"123.0.0.0"
}
}No further action — outcome: authenticated
outcome: authenticatedIf the provider and issuer find this information sufficient, the verification completes frictionlessly — no further step:
{
"id": "fingerprint",
"subaccountId": "f297d659-c13d-4219-aeaa-e10a845140a5",
"verificationId": "9fab1bea-bc1e-4757-bd47-479422e5983b",
"type": "fingerprint",
"state": "completed",
"outcome": "authenticated",
"createdAt": "2024-01-05T14:46:50.000Z",
"updatedAt": "2024-01-05T14:46:51.000Z",
"_links": {
"self": {
"href": "/card-verifications/9fab1bea-bc1e-4757-bd47-479422e5983b/3ds/steps/fingerprint"
},
"nextStep": {
"href": "/card-verifications/9fab1bea-bc1e-4757-bd47-479422e5983b/3ds/steps/challenge"
}
}
}Challenge is required — outcome: requires-challenge
outcome: requires-challengeIf the issuer still needs the cardholder to complete a challenge, the response says so:
If this is the case, follow the steps outlined in 3.2.
{
"id": "fingerprint",
"subaccountId": "f297d659-c13d-4219-aeaa-e10a845140a5",
"verificationId": "9fab1bea-bc1e-4757-bd47-479422e5983b",
"type": "fingerprint",
"state": "completed",
"outcome": "requires-challenge",
"createdAt": "2024-01-05T14:46:50.000Z",
"updatedAt": "2024-01-05T14:46:51.000Z",
"_links": {
"self": {
"href": "/card-verifications/9fab1bea-bc1e-4757-bd47-479422e5983b/3ds/steps/fingerprint"
},
"nextStep": {
"href": "/card-verifications/9fab1bea-bc1e-4757-bd47-479422e5983b/3ds/steps/challenge"
}
}
}Verification failed — outcome: failed
outcome: failedA terminal state: the verification ends as failed with no further step to call. Common causes: a lost/stolen card, an inactive card, or a card otherwise unable to transact.
{
"id": "fingerprint",
"subaccountId": "f297d659-c13d-4219-aeaa-e10a845140a5",
"verificationId": "9fab1bea-bc1e-4757-bd47-479422e5983b",
"type": "fingerprint",
"state": "completed",
"outcome": "failed",
"createdAt": "2024-01-05T14:46:50.000Z",
"updatedAt": "2024-01-05T14:46:51.000Z",
"_links": {
"self": {
"href": "/card-verifications/9fab1bea-bc1e-4757-bd47-479422e5983b/3ds/steps/fingerprint"
}
}
}3.2 3DS Challenge
In some instances of single card enrollment where device fingerprinting information needs to be complemented with more data, the cardholder will need to provide this additional verification data to their issuer through the embedded iframe portal. The card holder will be prompted with a token-based challenge, which they will need to provide through this iframe.
When the token-based challenge is executed, it will go to the registered cardholder, generally in the form of a push notification on the issuer app, or text SMS.
The iframe is completely controlled by the card verification provider, and therefore it is expected that you use the URL that is provided by them (example below).
The URL for the challenge iframe comes from the Get Card Verification step challenge data endpoint — the acsUrl property nested under data.
GET https://api.astrada.co/card-verifications/3ds/{verificationId}/steps/challenge{
"id": "challenge",
"subaccountId": "f297d659-c13d-4219-aeaa-e10a845140a5",
"verificationId": "9fab1bea-bc1e-4757-bd47-479422e5983b",
"type": "challenge",
"state": "in-progress",
"data": {
"acsUrl": "https://acs-public.tp.mastercard.com/api/v1/browser_challenges",
"encodedCReq": "eyJ0aHJlZURTU2VydmVyVHJhbnNJRCI6IjkzYTdjYzc1LWNiN2MtNGNkMy1hMDUzLWIyYzRjMTg2YmU1ZiIsImFjc1RyYW5zSUQiOiJiODBkNTZkNy01N2I1LTRhMzAtYmYwZC0xNzE4ZDlmNzI1ZTYiLCJjaGFsbGVuZ2VXaW5kb3dTaXplIjoiMDQiLCJtZXNzYWdlVHlwZSI6IkNSZXEiLCJtZXNzYWdlVmVyc2lvbiI6IjIuMi4wIn0"
},
"createdAt": "2024-01-05T14:46:51.000Z",
"updatedAt": "2024-01-05T18:47:22.000Z",
"_links": {
"self": {
"href": "/card-verifications/9fab1bea-bc1e-4757-bd47-479422e5983b/3ds/steps/challenge"
}
}
}The SDK displays the 3DS challenge iframe with HTML like the snippet below.
<html>
<head>
<script src="/static/challenge.js"></script>
<script>
window.onload = function() {
doChallenge('{{ acsUrl }}', '{{ encodedCReq }}');
}
</script>
</head>
<body>
Performing 3DS challenge.
</body>
</html>
On page load it calls doChallenge javascript function:
// This listener receives messages posted to the window. After listening
// threeds-challenge-notification message, the challenge results window will pop-up.
function challengeCompleteListener(m) {
if (m.data.type === 'threeds-challenge-notification') {
console.log("challengeCompleteListener called");
post("https://api.astrada.co/card-verifications/3ds/{verification-id}/steps/challenge", {});
}
};
// Opens 3DS challenge iframe and listens to event completion.
function doChallenge(acsUrl, encodedCReq) {
const html = `<script>
document.addEventListener("DOMContentLoaded", function () {
var form = document.createElement("form");
form.method = "POST";
form.action = "${acsUrl}";
form.appendChild(createInput("creq", "${encodedCReq}"));
document.body.appendChild(form);
form.submit();
document.body.removeChild(form);
});
function createInput(name, value) {
var result = document.createElement("input");
result.name = name;
result.value = value;
return result;
}
</script>`
const iframe = document.createElement("iframe");
iframe.id = "3ds-challenge";
iframe.width = "600px";
iframe.height = "400px";
iframe.frameBorder = "0";
iframe.style.display = 'block';
iframe.style.position = 'absolute';
iframe.style.top = "100px";
iframe.style.left = "50%";
iframe.style.transform = "translate(-50%, 0%)";
iframe.style.background = "white";
document.body.appendChild(iframe);
const win = iframe.contentWindow;
if (win != null) {
const doc = win.document;
win.name = "3DS Challenge";
doc.open();
doc.write(html);
doc.close();
}
window.addEventListener("message", challengeCompleteListener);
};
Once the challenge iframe is complete it posts a message to the window (threeDSMethodNotificationURL) to get the results of the challenge (step 5 below).
4. Success & Completion
Once the cardholder has successfully provided the challenge data to their issuer, a success state is returned.
409 Conflicton the challenge POST.POST /card-verifications/3ds/{verificationId}/steps/challengereturns409if the verification has been abandoned, or if the challenge results are not yet ready when the call is made. Retry shortly, or refresh the verification state viaGET /card-verifications/3ds/{verificationId}.
The SDK completes the verification with the following request when the challenge posts back, then checks state for completed or failed.
POST https://api.astrada.co/card-verifications/3ds/{verificationId}/steps/challenge{
"id": "challenge",
"subaccountId": "f297d659-c13d-4219-aeaa-e10a845140a5",
"verificationId": "9fab1bea-bc1e-4757-bd47-479422e5983b",
"type": "challenge",
"state": "completed",
"createdAt": "2024-01-05T14:46:51.000Z",
"updatedAt": "2024-01-05T18:47:22.000Z",
"_links": {
"self": {
"href": "/card-verifications/9fab1bea-bc1e-4757-bd47-479422e5983b/3ds/steps/challenge"
}
}
}Errors
Card Enrollment
| Validation | HTTP | Message detail (API) | Message detail (SDK) |
|---|---|---|---|
| Incorrect subaccount Id | 400 | Invalid uuid | Something went wrong |
| Ineligible card country of issuance | 400 | The card country of issuance (XX) is not supported (subaccount_blocking_card_country) | Something went wrong |
| Incorrect expiry date | 400 | Expiry date cannot be in the past | Something went wrong |
| Incorrect / Invalid PAN | 400 | Invalid PAN | Something went wrong |
| Incorrect / Invalid CVC | 400 | CVC must be 3 or 4 digits | Something went wrong |
| Card subscription already exists | 409 | There is already a card subscription for the specified card | You're all set |
| Card not supported | 400 | Invalid PAN | Something went wrong |
| 3DS challenge failed (provider-specific) | 400 | Card verification step with id=challenge failed... 3DS details: '[reason]' | Something went wrong |
Consent
What is Card Holder Consent?
Card holder consent refers to the approval a card holder gives to allow their transaction data to be accessed and used by third parties like Astrada. This consent is essential for complying with card network requirements and ensuring data security.
Why We Collect Consent
Contractual Requirement from Card Networks
Card networks mandate obtaining card holder consent to ensure that their transaction data is shared responsibly and ethically.
Through Astrada's APIs and SDK, card holders can opt in and authorize the sharing of their data. This opt-in process is crucial for Astrada and our customers to access such data legitimately.
Data Security and Best Practices
Collecting consent ensures that sensitive card holder data is not accessed or shared inappropriately, adhering to stringent data security standards.
How We Collect Consent
Astrada initiates the consent collection process when a customer enrolls a card for the first time.
Consent is gathered through a clear and conspicuous request, ensuring the card holder is fully informed and has the freedom to either consent or refuse. The request will explain the purpose of data collection and the specifics of how data will be used.
We use consent language approved by card networks to ensure uniformity and compliance (see Installation). This language is integrated into our card enrollment SDK by default.
Card holders must agree to Network-specific and Astrada-specific terms separately, ensuring clarity and compliance with privacy laws.
Upon receiving affirmative opt-in consent, Astrada verifies the identity of the card holder to ensure the consent is valid and associated with the correct individual.
We maintain detailed records of consents, including date and time stamps, to comply with legal requirements and for audit purposes.
By integrating with Astrada, our customers ensure that all data is fully compliant with both legal and network requirements.