Headless Payments (Frontend Integration)
This is a frontend integration guide. It covers building your own checkout UI in the browser — collecting card data with @tagadapay/core-js, handling 3DS challenges, and processing payments via the REST API.Looking for something else?
Just need a checkout link? Use the Merchant Quick Start — create a funnel + checkout session in 7 API calls with the Node SDK. No frontend code needed.
Server-to-server only (MIT)? Once you have a stored payment instrument, you can charge it directly from the Node SDK with tagada.payments.process() — no browser required.
When to Use This
Use case Example Custom checkout UI You build your own payment form and want full control over the UX Mobile apps Native iOS/Android apps that collect card data via your own UI Embedded checkout Injecting a payment form into a third-party page or iframe Marketplace / platform Collecting card details on behalf of sub-merchants
When NOT to Use This
Instead of… Use… You just want a hosted checkout page Merchant Quick Start — 7 API calls, zero frontend codeYou want a custom-branded page (HTML or Plugin SDK) Funnel Pages — deploy pages as pluginsYou need server-side recurring billing Subscriptions Guide — managed subscription lifecycleYou need to charge a stored card without a browser Node SDK tagada.payments.process() with initiatedBy: 'merchant' — see MIT examples below
The Flow
Every payment follows 4 steps:
1. Tokenize card → Client-side, via @tagadapay/core-js
2. Create instrument → Server-side, POST to TagadaPay API
3. 3DS authenticate → Client + Server (if required by the card issuer)
4. Process payment → Server-side, POST to TagadaPay API
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 1. Tokenize │────▶│ 2. Instrument│────▶│ 3. 3DS │────▶│ 4. Charge │
│ (client) │ │ (server) │ │ (client+srv) │ │ (server) │
└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘
Card details TagadaToken Challenge if Amount + currency
→ Secure Vault → Payment Inst. required → PSP routing
→ TagadaToken + Customer → Transaction
Prerequisites
A TagadaPay account with an API key (Bearer token)
A store with at least one payment flow configured (connects to your PSP: Stripe, NMI, Airwallex, etc.)
Install the tokenization SDK:
npm install @tagadapay/core-js
The tokenization SDK runs client-side (browser). Steps 2–4 happen server-side with your API key. Never expose your API key in the browser.
Step 1: Tokenize the Card (Client-Side)
The card number never touches your server. It goes directly to a PCI-compliant secure vault (BasisTheory) and returns a token.
React
Vanilla JavaScript
Apple Pay / Google Pay
import { useCardTokenization } from '@tagadapay/core-js/react' ;
function CheckoutForm () {
const { tokenizeCard , isLoading , error } = useCardTokenization ({
environment: 'production' , // 'production' | 'development' | 'local'
});
async function handleSubmit () {
const { tagadaToken , rawToken } = await tokenizeCard ({
cardNumber: '4242424242424242' ,
expiryDate: '12/28' ,
cvc: '123' ,
cardholderName: 'Jane Doe' ,
});
// tagadaToken → base64 string, send to your server
// rawToken.metadata.auth.scaRequired → true if 3DS is needed
await sendToServer ( tagadaToken , rawToken );
}
}
import { Tokenizer } from '@tagadapay/core-js' ;
const tokenizer = new Tokenizer ({ environment: 'production' });
await tokenizer . initialize ();
const tagadaToken = await tokenizer . tokenizeCard ({
cardNumber: '4242424242424242' ,
expiryDate: '12/28' ,
cvc: '123' ,
cardholderName: 'Jane Doe' ,
});
// tagadaToken → base64 string, send to your server
For the raw token with SCA metadata: const rawToken = await tokenizer . tokenizeCardRaw ({
cardNumber: '4242424242424242' ,
expiryDate: '12/28' ,
cvc: '123' ,
});
if ( rawToken . metadata ?. auth ?. scaRequired ) {
console . log ( '3DS will be required' );
}
import { useCardTokenization } from '@tagadapay/core-js/react' ;
const { tokenizeApplePay , tokenizeGooglePay } = useCardTokenization ({
environment: 'production' ,
});
// Apple Pay
const appleResult = await tokenizeApplePay ( applePayToken );
// Google Pay
const { tagadaToken } = await tokenizeGooglePay ( googlePayToken );
See the Apple & Google Pay example for the full integration including wallet button setup.
What’s in a TagadaToken? It’s a base64-encoded JSON envelope containing the provider token, card metadata (last4, brand, BIN, expiry), SCA flags, and issuer information. It’s safe to send over the network — no raw card data.
Step 2: Create a Payment Instrument (Server-Side)
Send the tagadaToken from your client to your server, then call TagadaPay to create a reusable payment instrument:
curl -X POST https://app.tagadapay.com/api/public/v1/payment-instruments/create-from-token \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"tagadaToken": "eyJ0eXBlIjoiY2FyZC...",
"storeId": "store_abc123",
"customerData": {
"email": "jane@example.com",
"firstName": "Jane",
"lastName": "Doe"
}
}'
Response:
{
"paymentInstrument" : {
"id" : "pi_a1b2c3d4" ,
"type" : "card" ,
"customerId" : "cus_x1y2z3" ,
"card" : {
"last4" : "4242" ,
"brand" : "visa" ,
"expMonth" : 12 ,
"expYear" : 2028
}
},
"customer" : {
"id" : "cus_x1y2z3" ,
"email" : "jane@example.com"
}
}
If the customer already exists, pass customerId instead of customerData. The instrument will be attached to the existing customer.
Save the paymentInstrument.id — you’ll use it for every charge on this card. The instrument is reusable for future payments (subscriptions, repeat purchases).
Step 3: 3DS Authentication (If Required)
3DS (3D Secure) is required by many card issuers, especially in Europe (SCA regulation). The tokenization step tells you if it’s needed via rawToken.metadata.auth.scaRequired.
3DS has two parts: creating a session (server + client) and handling the challenge (client).
3a. Create a 3DS Session
Client-side: Create a local session with the 3DS provider SDK:
import { useThreeds } from '@tagadapay/core-js/react' ;
const { createSession , startChallenge } = useThreeds ({
environment: 'production' ,
});
const session = await createSession (
{
id: paymentInstrument . id ,
token: rawToken . id ,
type: 'card' ,
card: {
last4: '4242' ,
bin: '424242' ,
expirationMonth: 12 ,
expirationYear: 2028 ,
},
},
{
amount: 2999 , // in cents
currency: 'USD' ,
customerInfo: {
name: 'Jane Doe' ,
email: 'jane@example.com' ,
},
},
);
Server-side: Persist the session to TagadaPay so the payment processor can use it:
curl -X POST https://app.tagadapay.com/api/public/v1/threeds/create-session \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"provider": "basis_theory",
"storeId": "store_abc123",
"paymentInstrumentId": "pi_a1b2c3d4",
"sessionData": {
"sessionId": "bt_session_xyz",
"metadata": {}
}
}'
Response:
{
"id" : "threeds_abc123" ,
"externalSessionId" : "bt_session_xyz" ,
"provider" : "basis_theory" ,
"status" : "created" ,
"paymentInstrumentId" : "pi_a1b2c3d4"
}
3b. Handle the Challenge (After Payment — See Step 4)
If the issuer requires a challenge (password, SMS, biometric), it comes back in the payment response. You handle it client-side — see Step 4b below.
Step 4: Process the Payment (Server-Side)
4a. Send the charge request
curl -X POST https://app.tagadapay.com/api/public/v1/payments/process \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": 2999,
"currency": "USD",
"storeId": "store_abc123",
"paymentInstrumentId": "pi_a1b2c3d4",
"threedsSessionId": "threeds_abc123",
"initiatedBy": "customer",
"mode": "purchase"
}'
Field Type Description amountnumberAmount in cents (2999 = $29.99) currencystringISO currency code (USD, EUR, GBP, etc.) storeIdstringYour store ID paymentInstrumentIdstringFrom Step 2 threedsSessionIdstringFrom Step 3 (optional if 3DS not required) initiatedBystring"customer" (CIT) or "merchant" (MIT)modestring"purchase" (charge immediately), "auth" (authorize only), or "capture" (capture a previous auth)reasonTypestringFor MIT only: "recurring", "unscheduled", or "installment" paymentFlowIdstringOptional — forces a specific payment flow (PSP routing rule)
If payment succeeds immediately:
{
"payment" : {
"id" : "pay_xyz789" ,
"amount" : 2999 ,
"currency" : "USD" ,
"status" : "succeeded" ,
"requireAction" : "none"
}
}
If 3DS challenge is required:
{
"payment" : {
"id" : "pay_xyz789" ,
"status" : "pending" ,
"requireAction" : "threeds_auth" ,
"requireActionData" : {
"type" : "threeds_auth" ,
"metadata" : {
"threedsSession" : {
"externalSessionId" : "bt_session_xyz" ,
"acsChallengeUrl" : "https://acs.issuer.com/challenge" ,
"acsTransID" : "acs_trans_123" ,
"messageVersion" : "2.2.0"
}
}
}
}
}
4b. Handle the 3DS Challenge (Client-Side)
When the payment response has requireAction: "threeds_auth", show the challenge to the customer:
const challengeResult = await startChallenge ({
sessionId: threedsSession . externalSessionId ,
acsChallengeUrl: threedsSession . acsChallengeUrl ,
acsTransactionId: threedsSession . acsTransID ,
threeDSVersion: threedsSession . messageVersion ,
});
if ( challengeResult . success ) {
// 3DS passed — poll for final payment status
}
The SDK opens a modal with the issuer’s authentication page. Once the customer completes it, poll for the final status.
4c. Poll for Final Status
After 3DS completes, the PSP processes the payment asynchronously. Poll until you get a terminal status:
curl https://app.tagadapay.com/api/public/v1/payments/pay_xyz789 \
-H "Authorization: Bearer YOUR_API_KEY"
Poll every 1–2 seconds. Terminal statuses: succeeded, failed, declined.
CIT vs MIT (Customer vs Merchant Initiated)
CIT (Customer Initiated) MIT (Merchant Initiated) When Customer is present and clicks “Pay” Server charges without customer present Examples First purchase, one-click reorder Subscription renewal, metered billing, retry 3DS Required if issuer demands it Typically exempt (uses stored credential) initiatedBy"customer""merchant"reasonTypeNot needed "recurring", "unscheduled", or "installment"
CIT Example (Customer Paying Now)
{
"amount" : 2999 ,
"currency" : "USD" ,
"storeId" : "store_abc123" ,
"paymentInstrumentId" : "pi_a1b2c3d4" ,
"initiatedBy" : "customer" ,
"mode" : "purchase"
}
MIT Example (Subscription Renewal)
{
"amount" : 2999 ,
"currency" : "USD" ,
"storeId" : "store_abc123" ,
"paymentInstrumentId" : "pi_a1b2c3d4" ,
"initiatedBy" : "merchant" ,
"reasonType" : "recurring" ,
"mode" : "purchase"
}
MIT Example (Auth + Capture)
// Step 1: Authorize
{
"amount" : 2999 ,
"currency" : "USD" ,
"storeId" : "store_abc123" ,
"paymentInstrumentId" : "pi_a1b2c3d4" ,
"initiatedBy" : "merchant" ,
"reasonType" : "unscheduled" ,
"mode" : "auth"
}
// Step 2: Capture later
{
"paymentId" : "pay_xyz789" ,
"mode" : "capture"
}
Void and Refund
Void (Cancel an Authorization)
curl -X POST https://app.tagadapay.com/api/public/v1/payments/void \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "paymentId": "pay_xyz789", "storeId": "store_abc123" }'
Refund (Full or Partial)
curl -X POST https://app.tagadapay.com/api/public/v1/payments/refund \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"paymentId": "pay_xyz789",
"storeId": "store_abc123",
"amount": 1000
}'
Omit amount for a full refund.
What TagadaPay Does Under the Hood
When you call /payments/process, TagadaPay:
Routes to the right PSP — based on your payment flow rules (card brand, currency, amount, BIN range, etc.)
Attaches 3DS data — if a threedsSessionId is provided, the authentication result is forwarded to the PSP
Handles retries — if the primary PSP declines, the payment flow can cascade to a backup PSP
Normalizes the response — regardless of which PSP handled it, you get the same response format
Records the transaction — for analytics, dispute management, and reconciliation
You don’t need to integrate with Stripe, NMI, Airwallex, etc. individually. Configure the PSP connections in the CRM dashboard , set up payment flow routing rules, and TagadaPay handles the rest.
API Reference
Endpoint Method Purpose /payment-instruments/create-from-tokenPOST Create instrument from TagadaToken /payment-instruments/{id}GET Get instrument details /customers/{id}/payment-instrumentsGET List customer’s instruments /threeds/create-sessionPOST Create 3DS session /payments/processPOST Process a payment (CIT or MIT) /payments/{id}GET Get payment status /payments/voidPOST Void an authorization /payments/refundPOST Full or partial refund
Working Examples
Complete working examples with React, TypeScript, and Tailwind CSS are available on GitHub:
Card Tokenization + Payment Full 4-step payment flow with 3DS
Apple Pay & Google Pay Wallet tokenization integration
Test card: 4242 4242 4242 4242 (any future expiry, any CVC)
SDK Reference
@tagadapay/core-js Package
Export Import Path What it provides Core @tagadapay/core-jsTokenizer class, createTagadaToken, decodeTagadaTokenReact @tagadapay/core-js/reactuseCardTokenization, useThreeds hooks3DS @tagadapay/core-js/threedsThreedsManager, ThreedsModal for manual 3DS
Key Types
interface CardPaymentMethod {
cardNumber : string ;
expiryDate : string ; // "MM/YY"
cvc : string ;
cardholderName ?: string ;
}
interface CardTokenResponse {
id : string ;
type : string ;
data : { number ?: string ; expiration_month ?: number ; expiration_year ?: number };
metadata ?: {
auth ?: { scaRequired ?: boolean };
tokenizedAt ?: string ;
};
}
interface TagadaToken {
type : 'card' | 'apple_pay' | 'google_pay' ;
token : string ;
provider : string ;
nonSensitiveMetadata : {
last4 ?: string ;
brand ?: string ;
bin ?: string ;
expiryMonth ?: number ;
expiryYear ?: number ;
funding ?: string ;
fingerprint ?: string ;
authentication ?: 'sca_required' | 'optional' ;
issuer ?: { name ?: string ; country ?: string };
};
}
npm Package @tagadapay/core-js on npm
API Reference Full REST API documentation