Skip to main content

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 caseExample
Custom checkout UIYou build your own payment form and want full control over the UX
Mobile appsNative iOS/Android apps that collect card data via your own UI
Embedded checkoutInjecting a payment form into a third-party page or iframe
Marketplace / platformCollecting card details on behalf of sub-merchants

When NOT to Use This

Instead of…Use…
You just want a hosted checkout pageMerchant Quick Start — 7 API calls, zero frontend code
You want a custom-branded page (HTML or Plugin SDK)Funnel Pages — deploy pages as plugins
You need server-side recurring billingSubscriptions Guide — managed subscription lifecycle
You need to charge a stored card without a browserNode 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.
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);
  }
}
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"
  }'
FieldTypeDescription
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)
WhenCustomer is present and clicks “Pay”Server charges without customer present
ExamplesFirst purchase, one-click reorderSubscription renewal, metered billing, retry
3DSRequired if issuer demands itTypically 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:
  1. Routes to the right PSP — based on your payment flow rules (card brand, currency, amount, BIN range, etc.)
  2. Attaches 3DS data — if a threedsSessionId is provided, the authentication result is forwarded to the PSP
  3. Handles retries — if the primary PSP declines, the payment flow can cascade to a backup PSP
  4. Normalizes the response — regardless of which PSP handled it, you get the same response format
  5. 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

EndpointMethodPurpose
/payment-instruments/create-from-tokenPOSTCreate instrument from TagadaToken
/payment-instruments/{id}GETGet instrument details
/customers/{id}/payment-instrumentsGETList customer’s instruments
/threeds/create-sessionPOSTCreate 3DS session
/payments/processPOSTProcess a payment (CIT or MIT)
/payments/{id}GETGet payment status
/payments/voidPOSTVoid an authorization
/payments/refundPOSTFull 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

ExportImport PathWhat it provides
Core@tagadapay/core-jsTokenizer class, createTagadaToken, decodeTagadaToken
React@tagadapay/core-js/reactuseCardTokenization, useThreeds hooks
3DS@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