Server-to-server payments
Once you have a Processing Key (tp_sk_…) + a storeId, the entire payment lifecycle is yours. No checkout session, no funnel, no UI assumptions on our side.
The four steps
Step 1 — Tokenize the card (browser)
Use@tagadapay/core-js. The card data goes directly to BasisTheory’s vault — never to your server, never to ours:
Alternative: you already hold the card (server-side tokenization)
If you are already PCI-compliant and hold card numbers in your own environment (your own vault, your own collection form), you can skip the browser step entirely and hand the card to the SDK from your server. It tokenizes straight into Tagada’s PCI vault and returns a ready payment instrument — steps 1 and 2 in one call.Where the card goes.
createFromCard() posts the card from your process straight to Tagada’s
PCI vault over TLS, then sends only the resulting vault token to the Tagada API. The card number and
CVC never reach the Tagada API and are never logged. Your PCI scope is unchanged — it is already
whatever it was before, since you were holding the PAN; ours stays out of it.paymentInstrument.
Step 2 — Create a payment instrument (server)
Convert the single-usetagadaToken into a reusable paymentInstrument:
customer is reused if you provide a matching email — one customer can hold multiple instruments.
Address field names. The platform stores and validates addresses as
address1, address2, postal, city, state, country (ISO 3166-1 alpha-2), plus firstName / lastName. The SDK still accepts the legacy line1 / line2 / postalCode aliases and maps them for you, but prefer the canonical names.Addresses are optional — including for digital products. Neither
billingAddress nor shippingAddress is required to charge: selling digital goods with no shipping address works out of the box. If you do provide an address, it is only forwarded to the acquirer when it carries a valid ISO 3166-1 alpha-2 country — an incomplete address (e.g. missing country) is safely omitted from the acquirer call instead of failing the payment. Providing a complete billingAddress is still recommended: it improves issuer approval rates (AVS) and 3DS frictionless outcomes.Pass the shopper’s IP — you are the only one who can. On this server-to-server path your server calls Tagada, so what Tagada sees is your datacenter, not the shopper. Processors weigh the shopper IP heavily (Stripe Radar, Adyen risk, issuer scoring): forward it, together with the browser’s
User-Agent, in customer: { ipAddress, userAgent } on createFromToken() / createFromCard() and on payments.process(). Tagada never substitutes your server’s address; a value that is not a valid IP is refused (400). With Express, req.ip is the shopper only behind app.set('trust proxy', true); otherwise take the first entry of X-Forwarded-For. The context is kept on the customer, so later charges on the same customer inherit it, but send it on every charge you can: the shopper may have moved.Step 3 — 3DS (usually nothing to do)
Most integrations skip this step. Whether 3-D Secure runs, and how, is decided by the PSP/acquirer behind your TPA — and most of them handle it themselves. There are two ways it can happen:
Option 1 — PSP/acquirer-hosted 3DS (the default)
Modern PSPs (Adyen, Stripe, …) own the 3DS challenge. You don’t callthreeds.createSession. You just charge (Step 4) with a returnUrl; if the issuer requires SCA, payments.process comes back with requireAction: 'redirect' and a redirectUrl pointing at the processor’s own hosted 3DS page. You send the customer there, they authenticate, and they return to your returnUrl. Nothing to collect, nothing to pass through — the processor reconciles the authentication internally.
Always send the returnUrl. Without it, on Stripe the customer ends on a neutral page hosted by TagadaPay after authenticating instead of coming back to you. Either way you still have to send them to the redirectUrl: a payment whose redirect nobody follows stays pending.
This is the path the rest of this guide uses. If your TPA is on a hosted-3DS PSP, ignore the code below and go straight to Step 4.
Option 2 — Tagada standalone 3DS (the exception)
Some gateway-style processors (e.g. NMI) don’t host 3DS, but they do accept pre-authenticated 3DS values (CAVV, ECI, DS transaction id…) on the charge. For those — and only when the acquirer is compatible and takes those values as input — Tagada can run the 3DS flow for you, collect the values, and pass them raw to the acquirer. You opt in explicitly:Option 3 — Bring your own 3DS (you already run an MPI)
You collect cards and run 3-D Secure in your own PCI environment — your own 3DS server, Basis Theory 3DS, Ravelin, … — and only want Tagada to charge. Don’t run a second authentication: report the final values and Tagada stores them as an already-authenticated session.- Only
transStatusY/Acreate a usable session.N/U/R/Iare stored as failed and their values are never forwarded. dsTransIDis mandatory for an authenticated 3DS 2.x result. The session is still created without one — sessions are often created and never used — but its values are not forwarded to the charge, because acquirers reject an authenticated 2.x result with no DS transaction id.- The target processor must accept externally-authenticated values: NMI, RouteSense, Mastercard MPGS, Adyen, Airwallex. The processor needs
enable3DSon and the payment flowthreeDsEnabled. Stripe accounts connected directly (not through TagadaPay) ignore them and run their own 3DS (Option 1). - TagadaPay processors (Adyen, Stripe, Checkout.com): on a TagadaPay account whose PSP is Adyen, Stripe or Checkout.com, the values are used without
enable3DSorthreeDsEnabled, on customer-initiated card charges: the PSP receives them in place of Tagada’s request for its own 3DS, and can still ask the shopper to authenticate. They are not used, and the payment runs as if none had been sent (the PSP’s own 3DS included), for a 3RI session, a result other thanY/A, a CAVV that is not 28-character base64, adsTransIDthat is not a UUID, an ECI other than 01 / 02 / 05 / 06, a 3DS version other than 2.1.x / 2.2.x, a Cartes Bancaires card, an authentication older than 90 days, a merchant-initiated charge, a wallet payment or a $0 card verification. Stripe also skips them on a co-badged Cartes Bancaires card and on a charge without customer-initiated stored credentials; Checkout.com skips them on a card saved at Checkout.com. TagadaPay accounts on another PSP ignore them. - For a MIT on a card you authenticated once (3RI), create the session with
type: 'merchant'and charge withinitiatedBy: 'merchant'+reasonType. TagadaPay processors do not use 3RI sessions.
When does 3DS trigger? When the card flow has
threeDsEnabled: true (set per-merchant via Payment Flows) or the issuer requests SCA. How it runs is up to the processor: hosted PSPs return requireAction: 'redirect' (Option 1); gateways that need raw values use the standalone session (Option 2). When in doubt, skip this step and just handle the requireAction that the charge returns.Step 4 — Charge
Handling the response
Multi-TPA routing (cascade & weighted)
payments.process doesn’t hit a processor directly — it runs the payment
flow attached to the storeId. A single merchant can own several TPAs (e.g. an
Adyen sub-merchant and a Stripe Merchant-of-Record account) and route across
them with no code change on your side: same process call, different
storeId.
- Cascade (failover) — the flow has a primary processor and an ordered
fallback list (
fallbackMode: true). If the primary declines (non-terminal), Tagada automatically retries the same charge on the next TPA, with a fresh payment method per attempt. Your code just sees the finalcaptured. - Weighted — multiple processors with weights; each charge is routed to one TPA by weight.
A processing key restricted to one TPA resolves to that TPA’s merchant
account, so it can charge any store of that merchant — including a store
whose flow spans several of the merchant’s TPAs. You don’t need a separate key
per processor.
Auth + capture (split-flow)
Authorize now, capture later (hotels, car rentals, pre-orders, marketplaces):payments.cancel(auth.id) to release the authorization without capturing.
Refunds
Full refund:Merchant-initiated transactions (MIT)
For subscription renewals, retry logic, or any charge initiated without the customer present:Listing and retrieving payments
Webhooks
To learn about asynchronous events (3DS callbacks, settlement, disputes), set up webhooks at the TPA level:Errors you should handle
The SDK throws typed errors:
