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:

GuideWhy
Getting StartedPlatform overview and setup
Api BasicsRequired headers and request configuration
AuthenticationHow to obtain access tokens
OnboardingUser and wallet registration
KYCKYC verification requirements
Top-Up Card RegistrationRegister 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:

  1. Estimate — Size the card debit for the token amount the user wants to receive
  2. Execute — Start the pull from the registered card
  3. 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"
}
FieldTypeRequiredDescription
external_card_idstring (UUID)Yescard_id from card registration. Can be retrieved via GET {pci_base_url}/b2b/cards/topup
destination_amountstringYesToken amount to receive, as an integer string scaled by the token's decimals (e.g. "100000000" for 100 USDC)
destination_token_addressstringYesToken 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"
}
FieldTypeDescription
estimation_idstring (UUID)Identifier for this estimation
expires_atintegerUnix timestamp (seconds) when the estimation expires
source_amountfloatFiat amount the card will be debited
source_amount_precisestringSource amount in base units of the source currency
source_currencystringSource (fiat) currency code
destination_amountfloatToken amount the user receives
destination_amount_precisestringDestination amount in token base units
destination_token_addressstringDestination token contract address
destination_token_symbolstringDestination token symbol
fee_amountfloatFee amount denominated in fee_currency
fee_amount_precisestringFee amount in base units of the fee currency
fee_currencystringFee currency code
ratefloatCard-to-reference-currency conversion rate
providerstringProvider 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"
}
FieldTypeRequiredDescription
estimation_idstring (UUID)YesEstimation identifier
device_idstringNoClient 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"
  }
}
FieldTypeDescription
idstring (UUID)Top-up transaction identifier
statusstringStatus of the top-up after initiation
three_ds_stateobject3DS state, present when a 3DS challenge is required
three_ds_state.flowstring3DS flow: frictionless or challenge
three_ds_state.providerstringProvider that issued the 3DS challenge
three_ds_state.statusstringStatus of the 3DS challenge
three_ds_state.status_reasonstringReason for the current 3DS status
three_ds_state.urlstringURL 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:

FlowAction
frictionlessThe issuer approved without a challenge. No client action is required; the top-up proceeds.
challengeOpen 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

StepDescription
InitiatedTop-up request received
CardInFiat debited from the source card
ReviewCompliance review (automatic for most top-ups)
CryptoInTokens credited to the user's wallet
ReversalPresent only when the top-up is reversed

Webhook Fields

FieldTypeDescription
typestringCardTopup
statusenumPending, Completed, Failed
directionstringAlways Inbound for card top-up
source.typestringExternalCard
source.card.idstringRegistered card id (external_card_id)
source.card.pan_laststringLast 4 digits of the card
source.card.payment_systemstringVISA or MASTERCARD
destination.wallet.addressstringUser's wallet address
source_amountobjectFiat amount debited from the card
destination_amountobjectToken amount credited to the wallet

Error Handling

Estimation Errors

Error ReasonFieldDescription
ErrorMissingFieldexternal_card_idCard id is required
ErrorInvalidFieldexternal_card_idMust be a valid UUID
ErrorMissingFielddestination_amountDestination amount is required
ErrorInvalidFielddestination_amountMust be an integer string greater than zero
ErrorMissingFielddestination_token_addressDestination token address is required
ErrorInvalidFielddestination_token_addressMust be a valid address
ErrorNotFounddestination_token_addressDestination token was not found
ErrorNotFounduser_idUser account was not found or does not belong to you

Execution Errors

Error ReasonFieldDescription
ErrorMissingFieldestimation_idEstimation id is required
ErrorInvalidFieldestimation_idMust be a valid UUID
ErrorNotFoundestimation_idEstimation not found or expired
ErrorNotFounduser_idUser 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.