Visa/Mastercard Integration for Crypto Card
Direct integration with Visa or Mastercard as principal member — a process lasting 12-24 months with capital requirements of $2M+. The realistic path for a startup — working through a program manager or directly-connected issuer who already has network membership. This page describes the technical side of such integration.
Ecosystem Participants
To understand where to integrate, you need to understand the structure:
Visa/Mastercard Network
↓
Principal Member Bank (BIN owner)
↓
Card Program Manager (Marqeta, Stripe Issuing, Galileo)
↓
Your Crypto Platform
↓
End User
Principal Member — bank with direct Visa/MC membership. Issues BIN ranges. Program Manager — technological intermediary, provides API for card issuance, authorization processing. Takes on technical and partly regulatory obligations. Your Platform — handles crypto side: asset storage, conversion, user interface.
Marqeta — The Main Path
Marqeta is the leading card issuing API. Used by Cash App, DoorDash, Coinbase Card. Works as program manager: you issue cards through their API, they process authorizations and via Just-In-Time (JIT) funding request funds from you at each transaction moment.
JIT Funding — Key Concept
JIT (Just-In-Time) Funding — mechanism where Marqeta doesn't hold user balance. Instead: at each card authorization, Marqeta makes webhook request to your API, you respond approve/decline with amount. This lets you apply your business logic (check crypto balance, convert) in real time.
// Your JIT Funding endpoint
app.post("/marqeta/jit-funding", async (req, res) => {
const { token, type, amount, currency_code, card_token } = req.body;
// This webhook from Marqeta — need to respond in 2-3 seconds max
const userId = await getUserByCardToken(card_token);
const user = await getUser(userId);
// Convert USD amount to crypto
const cryptoAmount = await convertUSDToCrypto(amount, user.preferredAsset);
// Check and reserve balance
const hasBalance = await reserveBalance(userId, cryptoAmount);
if (!hasBalance) {
return res.json({
jit_funding: {
token: token,
method: "pgfs.authorization",
user_token: userId,
amount: 0,
},
// decline
});
}
return res.json({
jit_funding: {
token: token,
method: "pgfs.authorization",
user_token: userId,
amount: amount,
currency_code: "USD",
},
});
});
Marqeta API — Card Issuance
import Marqeta from "@marqeta/core-api";
const marqeta = new Marqeta({
applicationToken: process.env.MARQETA_APP_TOKEN,
accessToken: process.env.MARQETA_ACCESS_TOKEN,
baseUrl: "https://sandbox.marqeta.com/v3",
});
// Creating user in Marqeta
async function createMarqetaUser(userId: string, userData: UserData) {
const marqetaUser = await marqeta.users.create({
token: userId,
first_name: userData.firstName,
last_name: userData.lastName,
email: userData.email,
birth_date: userData.birthDate,
address1: userData.address,
city: userData.city,
state: userData.state,
country: userData.country,
postal_code: userData.postalCode,
});
return marqetaUser;
}
// Issuing virtual card
async function issueVirtualCard(userId: string) {
const card = await marqeta.cards.create({
user_token: userId,
card_product_token: process.env.CARD_PRODUCT_TOKEN,
});
// Getting PAN (card number) through secure channel
const cardDetails = await marqeta.cards.getShowPAN(card.token);
return {
cardToken: card.token,
last4: card.last_four,
expiration: card.expiration,
pan: cardDetails.pan, // for display in app only
cvv2: cardDetails.cvv_number, // for display in app only
};
}
3DS (3D Secure) for Online Transactions
For online purchases (without physical POS terminal) 3DS verification is required. Marqeta supports 3DS 2.x via separate endpoint:
// 3DS Challenge handler
app.post("/marqeta/3ds-challenge", async (req, res) => {
const { transaction_token, card_token, merchant } = req.body;
// Send user push/SMS for confirmation
await sendOTPToUser(card_token, transaction_token);
// Response: request OTP (challenge) or approve without challenge (frictionless)
res.json({
three_ds_decision: "CHALLENGE",
otp_required: true,
});
});
Stripe Issuing — Alternative
Stripe Issuing is simpler to integrate, available in 30+ countries. Doesn't support JIT funding fully — balance must be pre-funded on Stripe account. This means you need to hold fiat reserve at Stripe, which complicates crypto integration.
Suitable for: B2B expense management cards, corporate cards with crypto balance. Not optimal for consumer crypto debit cards.
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
// Card issuance
const card = await stripe.issuing.cards.create({
cardholder: cardholderId,
currency: "usd",
type: "virtual",
status: "active",
spending_controls: {
spending_limits: [{ amount: 50000, interval: "daily" }], // $500/day
},
});
Authorizations and Settlement
Important to understand the difference:
Authorization — checking and locking funds. Happens instantly. Your JIT endpoint must respond in 2-3 seconds.
Clearing/Settlement — actual deduction. Happens in 1-3 business days. Until then, amount is "frozen".
Refund/Reversal — transaction cancellation. Can come days after authorization. System must handle hold revert.
// Handling final settlement
app.post("/marqeta/webhook", async (req, res) => {
const event = req.body;
switch (event.type) {
case "authorization":
// Hold: reserve crypto
await holdCrypto(event.card_token, event.amount);
break;
case "authorization.clearing":
// Settlement: convert and finally deduct
await settleTransaction(event.transaction.token, event.amount);
break;
case "authorization.reversal":
// Cancellation: release locked crypto
await releaseHold(event.transaction.token);
break;
case "refund":
// Refund: credit crypto back
await refundCrypto(event.card_token, event.amount);
break;
}
res.status(200).send("OK");
});
Managing Currency Risk
When holding USD amounts in crypto equivalent — there's risk: rate changes between authorization (T+0) and settlement (T+1..3). Three approaches:
Stablecoin by default (USDC/USDT). No currency risk. Users hold stablecoins, payment is straightforward. Downside: no crypto upside.
Over-reservation. At authorization, reserve amount with buffer (+2-3%). At settlement, excess is returned. Simple solution for small volumes.
FX hedge. At authorization, fix rate through derivatives or quick CEX trade. More complex, pricey, but accurate. Needed for large volumes or volatile assets.
PCI DSS Compliance
Working with card numbers (PAN) requires PCI DSS certification. Levels:
- SAQ A — if you don't process PAN directly (Marqeta stores, you use tokens only): minimal requirements
- SAQ D — if you store/process PAN: full audit, expensive
Recommendation: use card tokenization (Marqeta Token Vault). Your system operates only with card_token, PAN never passes through your servers.
Regulatory Path
| Jurisdiction | Requirement | Timeline | Cost |
|---|---|---|---|
| EU | EMI license (Lithuania) | 6-12 months | $50-200k |
| UK | FCA EMI | 9-18 months | $100-300k |
| USA | MTL in each state | 12-24 months | $500k+ |
| Singapore | MAS Major Payment Institution | 12-18 months | $100-200k |
Minimum path for EU: Lithuania registration + passporting. For rest of world — partnership with already licensed issuer.
Development Timeline
- Marqeta integration (JIT + card issuance + webhooks): 4-6 weeks
- 3DS integration: +2-3 weeks
- Crypto custody + conversion: in parallel, 4-8 weeks
- KYC/AML: 2-4 weeks
- Mobile app: 8-12 weeks
- Compliance + testing: 4-6 weeks
- Total (technically): 5-7 months







