Card Top-Up

Estimate and execute a top-up that funds a user wallet from an external card, Apple Pay, or Google Pay.

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 funds a user's wallet with an on-chain token, debited from one of three instruments,
named by payment_method on the estimate:

  • external_card (default) — an external payment card (Visa/Mastercard) via AFT (Account Funding
    Transaction) rails, registered through Top-Up Card Registration
  • apple_pay — the card behind an Apple Pay device payment sheet
  • google_pay — the card behind a Google Pay device payment sheet

The top-up flow:

  1. Estimate — Size the debit for the token amount the user wants to receive, naming the instrument
  2. Execute — Start the pull from the registered card, or relay the payment-sheet payload for a wallet method
  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 instrument will be charged, the fee, and the exchange rate.

Capability Check

An external_card top-up requires the CardTopup capability to be active. An apple_pay or google_pay
top-up requires WalletTopup instead — a separate capability, so a partner can be live on card top-up while
wallet-pay enrolment is still pending, or the reverse. 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",
  "payment_method": "external_card"
}
FieldTypeRequiredDescription
external_card_idstring (UUID)Conditionalcard_id from card registration. Required when payment_method is external_card (or omitted); rejected for apple_pay / google_pay. 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
payment_methodstringNoexternal_card (default), apple_pay, or google_pay. Any other value returns 400 ErrorInvalidField

Trap: payment_method is the only place the instrument is named. Execute takes only estimation_id —
it reads the instrument off the estimation it references, so a wallet-method execute never repeats
payment_method.

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"
}

For an external_card estimation, estimation_id and device_id are the whole body. A wallet-method
estimation (apple_pay / google_pay) additionally carries the payload the device payment sheet returned:

{
  "estimation_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "device_id": "device-1234",
  "wallet_payment_token": "eyJzaWduYXR1cmUiOiJNRVU...",
  "cardholder_name": "Alex Grey",
  "billing_address": {
    "country_code": "GB",
    "city": "London",
    "postal_code": "EC1A 1BB",
    "address_line1": "123 Main St",
    "address_line2": "Apt 4B"
  },
  "wallet_transaction_id": "1a2b3c4d5e6f"
}
FieldTypeRequiredDescription
estimation_idstring (UUID)YesEstimation identifier
device_idstringNoClient device identifier forwarded to the underlying provider
wallet_payment_tokenstringConditionalThe encrypted payload from the device payment sheet, relayed verbatim. Required for a wallet-method estimation, rejected otherwise. Max 16384 characters
cardholder_namestringConditionalName on the instrument behind the payment sheet. Required for a wallet-method estimation, rejected otherwise
billing_addressobjectConditionalAddress the payment sheet returned. Required for a wallet-method estimation, rejected otherwise. When present, every subfield below except state and address_line2 is required
billing_address.country_codestringYes (within object)ISO 3166-1 alpha-2 country
billing_address.statestringNoState or province, where the country has one
billing_address.citystringYes (within object)City
billing_address.postal_codestringYes (within object)Postal code
billing_address.address_line1stringYes (within object)Street address
billing_address.address_line2stringNoAdditional address line
wallet_transaction_idstringNoTransaction identifier the payment sheet issued alongside the token. Rejected for an external_card estimation. Max 128 characters

Trap: which fields execute accepts follows the estimation's payment method, not anything on this
request — wallet_payment_token, cardholder_name, billing_address and wallet_transaction_id are all
rejected if the referenced estimation was external_card.

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
ErrorInvalidFieldpayment_methodMust be one of external_card, apple_pay, or google_pay
ErrorInvalidFieldexternal_card_idSent alongside apple_pay or google_pay — not allowed for a wallet payment method

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
ErrorInvalidFieldwallet_payment_tokenBlank, or longer than 16384 characters
ErrorInvalidFieldcardholder_nameDoes not match the expected name format
ErrorInvalidFieldwallet_transaction_idBlank, or longer than 128 characters
ErrorMissingFieldbilling_address.*A required subfield (country_code, city, postal_code, address_line1) is missing whenever billing_address is present
ErrorInvalidFieldbilling_address.*A subfield fails its format check whenever billing_address is present

An execute payload that contradicts its estimation's payment method (a card field on a wallet-method
estimation, or a wallet field on a card one) is rejected by the topup service.

Card top-up requires the CardTopup capability to be active for an external_card estimate and execute,
and WalletTopup for apple_pay / google_pay. Requests fail when the relevant 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 for external_card top-ups, and the
WalletTopup capability for apple_pay / google_pay top-ups. Check capabilities via GET /api/v2/user
before initiating top-ups.


Did this page help you?