Card Top-Up
Estimate and execute a top-up that funds a user wallet from an external card.
Before You Start
Read the following guides before proceeding:
| Guide | Why |
|---|---|
| Getting Started | Platform overview and setup |
| Api Basics | Required headers and request configuration |
| Authentication | How to obtain access tokens |
| Onboarding | User and wallet registration |
| KYC | KYC verification requirements |
| Top-Up Card Registration | Register the source card |
Overview
A card top-up debits an external payment card (Visa/Mastercard) using AFT (Account Funding Transaction) rails and credits the user's wallet with an on-chain token.
The top-up flow:
- Estimate — Size the card debit for the token amount the user wants to receive
- Execute — Start the pull from the registered card
- 3DS — Complete the 3DS challenge when required
The user specifies the destination token and amount they want to receive. The estimate returns the source (fiat) amount their card will be charged, the fee, and the exchange rate.
Capability Check
Card top-up requires the CardTopup capability to be active. Check capability status via GET /api/v2/user before initiating top-ups.
Estimate Top-Up
Get a top-up estimation for the token amount the user wants to receive.
Endpoint
POST /api/v1/cards/topup/estimate
Request
{
"external_card_id": "c34908da-d980-4a9c-9b39-4dabd6f6144e",
"destination_amount": "100000000",
"destination_token_address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
}| Field | Type | Required | Description |
|---|---|---|---|
external_card_id | string (UUID) | Yes | card_id from card registration. Can be retrieved via GET {pci_base_url}/b2b/cards/topup |
destination_amount | string | Yes | Token amount to receive, as an integer string scaled by the token's decimals (e.g. "100000000" for 100 USDC) |
destination_token_address | string | Yes | Token address the user wants to receive on chain |
Response
{
"estimation_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"expires_at": 1704110400,
"source_amount": 100.50,
"source_amount_precise": "100500000000000000000",
"source_currency": "EUR",
"destination_amount": 100.00,
"destination_amount_precise": "100000000",
"destination_token_address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"destination_token_symbol": "USDC",
"fee_amount": 0.50,
"fee_amount_precise": "500000000000000000",
"fee_currency": "EUR",
"rate": 1.0,
"provider": "checkout"
}| Field | Type | Description |
|---|---|---|
estimation_id | string (UUID) | Identifier for this estimation |
expires_at | integer | Unix timestamp (seconds) when the estimation expires |
source_amount | float | Fiat amount the card will be debited |
source_amount_precise | string | Source amount in base units of the source currency |
source_currency | string | Source (fiat) currency code |
destination_amount | float | Token amount the user receives |
destination_amount_precise | string | Destination amount in token base units |
destination_token_address | string | Destination token contract address |
destination_token_symbol | string | Destination token symbol |
fee_amount | float | Fee amount denominated in fee_currency |
fee_amount_precise | string | Fee amount in base units of the fee currency |
fee_currency | string | Fee currency code |
rate | float | Card-to-reference-currency conversion rate |
provider | string | Provider of the underlying top-up |
Execute Top-Up
Execute the top-up using a valid estimation.
Endpoint
POST /api/v1/cards/topup/execute
Request
{
"estimation_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"device_id": "device-1234"
}| Field | Type | Required | Description |
|---|---|---|---|
estimation_id | string (UUID) | Yes | Estimation identifier |
device_id | string | No | Client device identifier forwarded to the underlying provider |
Response
{
"id": "d4e5f6a7-b8c9-0123-def4-567890123456",
"status": "Pending",
"three_ds_state": {
"flow": "challenge",
"provider": "checkout",
"status": "pending",
"status_reason": "authentication_required",
"url": "https://3ds.example/challenge?session_token=abc123"
}
}| Field | Type | Description |
|---|---|---|
id | string (UUID) | Top-up transaction identifier |
status | string | Status of the top-up after initiation |
three_ds_state | object | 3DS state, present when a 3DS challenge is required |
three_ds_state.flow | string | 3DS flow: frictionless or challenge |
three_ds_state.provider | string | Provider that issued the 3DS challenge |
three_ds_state.status | string | Status of the 3DS challenge |
three_ds_state.status_reason | string | Reason for the current 3DS status |
three_ds_state.url | string | URL the client must open to complete the 3DS challenge |
3DS Handling
Card debits are subject to Strong Customer Authentication. When the execute response includes three_ds_state, complete 3DS based on the reported flow:
| Flow | Action |
|---|---|
frictionless | The issuer approved without a challenge. No client action is required; the top-up proceeds. |
challenge | Open three_ds_state.url so the cardholder can complete the issuer's 3DS challenge. |
Execute → three_ds_state?
├─ frictionless → top-up proceeds
└─ challenge → open url → cardholder authenticates → top-up proceeds
After 3DS is completed, the top-up settles and a CardTopup activity webhook is delivered. If three_ds_state is absent from the execute response, no 3DS step is required.
Transaction Webhook
After the top-up is processed, an activity webhook is delivered.
Endpoint: POST {base_url}/v2/webhooks/activities
{
"id": "d4e5f6a7-b8c9-0123-def4-567890123456",
"user_address": "0x7e1Cc1685D68D486b22D3880030C24434E3b90a9",
"type": "CardTopup",
"status": "Completed",
"direction": "Inbound",
"source": {
"type": "ExternalCard",
"card": {
"id": "c34908da-d980-4a9c-9b39-4dabd6f6144e",
"pan_last": "2701",
"payment_system": "VISA"
}
},
"destination": {
"type": "Wallet",
"wallet": {
"address": "0xAAFF0821A09A1Aac28B72dD3Ff410A7ea5FEb874"
}
},
"source_amount": {
"amount": 100.00,
"currency": "EUR"
},
"destination_amount": {
"amount": 100.00,
"token_symbol": "USDC",
"token_address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
},
"fee_amount": {
"amount": 0.50,
"currency": "EUR"
},
"created_at": "2024-01-01T13:00:00.000Z",
"activity_steps": [
{
"type": "Initiated",
"status": "Completed",
"created_at": "2024-01-01T13:00:00.000Z",
"completed_at": "2024-01-01T13:00:00.000Z"
},
{
"type": "CardIn",
"status": "Completed",
"created_at": "2024-01-01T13:00:00.000Z",
"completed_at": "2024-01-01T13:00:15.000Z"
},
{
"type": "Review",
"status": "Completed",
"created_at": "2024-01-01T13:00:15.000Z",
"completed_at": "2024-01-01T13:00:20.000Z"
},
{
"type": "CryptoIn",
"status": "Completed",
"created_at": "2024-01-01T13:00:20.000Z",
"completed_at": "2024-01-01T13:00:30.000Z"
}
]
}Activity Steps
| Step | Description |
|---|---|
Initiated | Top-up request received |
CardIn | Fiat debited from the source card |
Review | Compliance review (automatic for most top-ups) |
CryptoIn | Tokens credited to the user's wallet |
Reversal | Present only when the top-up is reversed |
Webhook Fields
| Field | Type | Description |
|---|---|---|
type | string | CardTopup |
status | enum | Pending, Completed, Failed |
direction | string | Always Inbound for card top-up |
source.type | string | ExternalCard |
source.card.id | string | Registered card id (external_card_id) |
source.card.pan_last | string | Last 4 digits of the card |
source.card.payment_system | string | VISA or MASTERCARD |
destination.wallet.address | string | User's wallet address |
source_amount | object | Fiat amount debited from the card |
destination_amount | object | Token amount credited to the wallet |
Error Handling
Estimation Errors
| Error Reason | Field | Description |
|---|---|---|
ErrorMissingField | external_card_id | Card id is required |
ErrorInvalidField | external_card_id | Must be a valid UUID |
ErrorMissingField | destination_amount | Destination amount is required |
ErrorInvalidField | destination_amount | Must be an integer string greater than zero |
ErrorMissingField | destination_token_address | Destination token address is required |
ErrorInvalidField | destination_token_address | Must be a valid address |
ErrorNotFound | destination_token_address | Destination token was not found |
ErrorNotFound | user_id | User account was not found or does not belong to you |
Execution Errors
| Error Reason | Field | Description |
|---|---|---|
ErrorMissingField | estimation_id | Estimation id is required |
ErrorInvalidField | estimation_id | Must be a valid UUID |
ErrorNotFound | estimation_id | Estimation not found or expired |
ErrorNotFound | user_id | User account was not found or does not belong to you |
Card top-up also requires the CardTopup capability to be active for both estimate and execute. Requests fail when the capability is inactive.
Error Response Format
{
"error_reason": "ErrorInvalidField",
"error_description": "external card id must be a valid UUID",
"error_category": {
"category": "CategoryValidationFailure",
"http_status_code": 400
},
"error_details": [
{
"key": "field",
"details": "external_card_id"
},
{
"key": "issue",
"details": "invalid_uuid"
}
]
}Capability
Card top-up requires the CardTopup capability to be active. Check capabilities via GET /api/v2/user before initiating top-ups.

