Documentation Index
Fetch the complete documentation index at: https://docs.cryptomate.me/llms.txt
Use this file to discover all available pages before exploring further.
This guide walks through the full lifecycle of a virtual card: from issuance and funding, through per-card configuration and spending controls, to fraud protection and transaction visibility. All operations are available both via the API (API key auth) and the CryptoMate Portal.
How it works
Each virtual card is a prepaid Visa card backed by stablecoin collateral. At the account level, a single holding wallet on the Polygon network holds all funds. When a purchase is made, the holding wallet balance is verified and the appropriate amount is debited.
Holding wallet (USDC, Polygon)
└── Virtual Card A ── purchase → authorization → settled
└── Virtual Card B ── purchase → authorization → settled
└── Virtual Card C ...
Cards are issued against a card pack. Each company has a maximum number of active cards based on their contracted product. You can check available capacity via Get contracted product.
Creating a card
Use Create virtual card to issue a new card.
The two required decisions at creation time are:
Approval method
| Value | How purchases are approved |
|---|
TOP_UP | The cardholder pre-loads funds onto the card’s individual deposit wallet. Purchases are auto-approved against that balance. |
WEBHOOK | Each purchase triggers a real-time webhook to your endpoint. You have 1,200 ms to respond with "00" / "SUCCESS" to approve or "05" to decline. Timeout = decline. |
The approval_method cannot be changed after card creation. If the wrong method is set, delete the card and reissue.
Spending limits
Set daily_limit, weekly_limit, and monthly_limit (in USD) at creation. These are enforced per card independently. You can update them at any time — see Spending limits below.
Omitting a limit means no limit is applied for that time window.
Funding the account
All cards draw from a shared holding wallet. Before cards can transact you must fund this wallet with USDC on Polygon.
Retrieve the holding wallet deposit address via Get holding balance. The minimum deposit is 10 USD.
A minimum balance is always locked in the holding wallet to preserve capacity for at least one transaction. Withdrawals that would breach this floor are rejected.
To return funds to your treasury, use Withdraw from holding.
For cards using TOP_UP approval, each card also has a personal deposit wallet. Fetch those addresses with Get card top-up wallets. Deposits sent to these wallets are attributed to that specific card and used to settle its purchases.
Managing cards
Spending limits
Update per-card daily, weekly, and monthly limits independently with Update card limits. Changes take effect immediately on the next authorization.
Freeze and unfreeze
Use Freeze or unfreeze card to temporarily block a card. A frozen card declines all purchase attempts until unfrozen. The card state moves between ACTIVE and FROZEN without losing its configuration.
Useful for: temporary employee suspension, suspicious activity hold, cardholder request.
Unblocking after a velocity block
If a velocity rule triggers, the card is automatically moved to BLOCKED. Manually unblock it with Unblock card. Unblocking also resets the authorization counter so the card can transact immediately.
Reissuing a card
Reissue card generates a new card number and CVV while keeping the same cardholder configuration, limits, and approval method. Use this when a card number is compromised. The old card is deactivated.
| What | Endpoint |
|---|
| Email for notifications | Update email |
| Phone for OTP / 3DS SMS | Update phone |
| Card PIN | Update PIN |
Rotating the deposit wallet
If a TOP_UP card’s deposit wallet address needs to be rotated (e.g. for compliance or security), use Rotate deposit wallet. A new address is generated and associated with the card. The old address stops accepting deposits.
Deleting a card
Delete card permanently deactivates the card. Any remaining balance in the card’s individual wallet is returned to the holding wallet. This action is irreversible.
3DS authentication
3DS adds cardholder verification to e-commerce purchases. Two modes are available per card:
| Mode | Behavior |
|---|
sms | A one-time code is sent to the cardholder’s registered phone. |
webhook | A webhook is sent to your endpoint with the OTP challenge. Your system delivers it via your own channel. |
Configure the mode with Set 3DS configuration.
When a 3DS challenge fires, a notification_3ds_authorization webhook is delivered to your endpoint. For webhook mode, use Respond to 3DS authorization to submit the OTP entered by the cardholder.
Fraud protection
Velocity rules
Velocity rules define rate limits at the account level, applied per individual card. They protect against card probing and rapid fraudulent use.
Each rule specifies:
max_authorizations — the maximum number of approved authorizations allowed
time_window_seconds — the sliding window over which the count is measured
You can configure up to 5 rules. When any rule is breached, the transaction is declined and the card is automatically blocked.
Manage rules with:
Example ruleset — no more than 3 purchases per minute, or 20 per hour:
{
"rules": [
{ "max_authorizations": 3, "time_window_seconds": 60 },
{ "max_authorizations": 20, "time_window_seconds": 3600 }
]
}
When a velocity block fires, a card_blocked_by_velocity webhook is sent to your endpoint.
Risk score engine
The risk score engine evaluates every purchase in real time. It assigns a numeric score by combining up to six signals, each weighted independently. If the score reaches or exceeds a configurable threshold, the transaction is declined.
How the score is calculated
Each signal is binary (0 or 1). The final score is the weighted sum:
score = (signal_geo × w_geo)
+ (signal_mcc × w_mcc)
+ (signal_amount × w_amount)
+ (signal_time × w_time)
+ (signal_declines × w_declines)
+ (signal_country × w_country)
A transaction is rejected when score ≥ threshold.
Signals
| Signal | ID | Fires (= 1) when… | Notes |
|---|
| Impossible travel | geo_distance | Card used in a different country less than 4 hours after the previous transaction | Compares merchant country of current vs. last approved transaction |
| Unfamiliar MCC | mcc_profile | Merchant category code was never seen in the card’s 90-day history | Requires ≥ 5 historical transactions to activate |
| Amount above baseline | amount_baseline | Purchase amount exceeds the 95th percentile of the card’s historical amounts | Requires ≥ 5 historical transactions to activate |
| Unusual hour | time_window | Transaction hour appears in less than 5% of the card’s historical transactions | Requires ≥ 5 historical transactions to activate |
| High decline rate | decline_rate | 3 or more transactions were declined on this card in the last 24 hours | Detects rapid retry / card probing |
| Foreign merchant country | merchant_country | Merchant’s country differs from the cardholder’s country (set via metadata.country at card creation) | Signal is 0 if metadata.country is not set |
Signals that require a minimum history return 0 (no contribution) until the card has enough approved transactions. A brand-new card is only evaluated by geo_distance, decline_rate, and merchant_country.
Configuration
The engine is configured at the account level from the Portal (Cards → Configuration → Risk Score). Each weight can be set to 0 to disable that signal entirely. Setting all weights to 0 disables the engine for the whole account.
| Field | Description |
|---|
threshold | Score at or above which a transaction is rejected. Range: 0.0 – any positive value. |
geo_distance_weight | Weight for the impossible travel signal. |
mcc_profile_weight | Weight for the unfamiliar MCC signal. |
amount_baseline_weight | Weight for the above-P95 amount signal. |
time_window_weight | Weight for the unusual hour signal. |
decline_rate_weight | Weight for the high recent decline rate signal. |
merchant_country_weight | Weight for the foreign merchant country signal. |
Examples
Conservative setup — only block clear anomalies, high threshold:
{
"threshold": 0.8,
"geo_distance_weight": 0.5,
"mcc_profile_weight": 0.2,
"amount_baseline_weight": 0.2,
"time_window_weight": 0.1,
"decline_rate_weight": 0.4,
"merchant_country_weight": 0.0
}
In this configuration a transaction is rejected only if, for example, both impossible travel (0.5) and high decline rate (0.4) fire simultaneously (score = 0.9 ≥ 0.8). A single signal is not enough to reach the threshold.
Strict setup — any single anomaly blocks the transaction:
{
"threshold": 0.3,
"geo_distance_weight": 1.0,
"mcc_profile_weight": 0.5,
"amount_baseline_weight": 0.5,
"time_window_weight": 0.3,
"decline_rate_weight": 1.0,
"merchant_country_weight": 0.3
}
Here, impossible travel alone (1.0) exceeds the threshold. Even an unusual hour (0.3) is enough to reject.
Disabled engine — all weights set to 0, no transaction is ever rejected by risk score:
{
"threshold": 1.0,
"geo_distance_weight": 0.0,
"mcc_profile_weight": 0.0,
"amount_baseline_weight": 0.0,
"time_window_weight": 0.0,
"decline_rate_weight": 0.0,
"merchant_country_weight": 0.0
}
Fail-safe behavior
The engine is designed to fail open: if the transaction history cannot be fetched, or if the configuration cannot be loaded, the risk score check is skipped and the transaction proceeds normally. This ensures that a Redis or database hiccup never causes unexpected declines.
Risk score fuse
The risk score engine evaluates each purchase and may decline transactions that appear high-risk. When a trusted cardholder is incorrectly blocked (false positive), you can arm a one-shot fuse that bypasses risk evaluation for the next single purchase on that card. After that purchase fires, the fuse is automatically consumed and normal validation resumes.
The fuse bypasses only the risk score check. All other controls (spending limits, velocity rules, frozen status, holding balance) still apply.
| Operation | Endpoint |
|---|
| Arm the fuse | Arm risk score fuse |
| Disarm before it fires | Disarm risk score fuse |
| Check fuse state | Get risk score fuse state |
Transactions
Searching transactions
Search transactions returns the transaction history for your account with filtering by card, date range, status, and more.
Get transaction returns the full detail of a single transaction by ID.
Accumulated spending
Get accumulated spending returns the total amount spent within a given period, useful for dashboards and limit management.
PDF statements
Export transactions PDF generates a downloadable statement for a date range.
Webhooks reference
Register your webhook URL and key via the Portal (Settings → Webhooks) or via Subscribe webhook URL.
All card events arrive with "product": "cards" in the envelope.
| Event | When it fires |
|---|
authorization | A purchase needs your approval (WEBHOOK mode). Respond within 1,200 ms with "00" / "SUCCESS" to approve or "05" to decline. |
authorized | A purchase was approved (TOP_UP / NONE mode, or your webhook approved it). |
cleared | An authorized purchase cleared and funds settled. |
declined | A purchase was declined (by your webhook, by velocity rules, by risk score, or by insufficient balance). |
reversal | An authorized purchase was reversed by the merchant or network. |
refund | A merchant refund was applied to the card. |
deposit | Funds arrived at a card’s top-up wallet. |
visa_direct_deposit | A Visa Direct deposit credited the card. |
warranty_withdraw | A withdrawal from the holding balance completed. |
notification_3ds_authorization | A 3DS challenge was triggered. Deliver the OTP to the cardholder. |
card_blocked_by_velocity | A velocity rule triggered and blocked the card. Sent once per ACTIVE → BLOCKED transition. |
See Webhooks for the full envelope format and authentication.
Quick reference