Issue and Manage a Card

Issue virtual and physical payment cards linked to user wallets.

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

Overview

Wirex BaaS supports issuing Visa payment cards linked to user Smart Wallets. Cards can be:

  • Virtual — Digital cards for online payments, issued instantly
  • Physical — Plastic cards delivered to the user's address

Cards are funded from the user's unified balance (WUSD/WEUR) and can be used anywhere Visa is accepted.


Prerequisites

Before issuing a card, check user eligibility via the user endpoint. Capabilities are returned as part of the user response.

GET /api/v2/user
const response = await fetch(`${baseUrl}/api/v2/user`, {
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'X-User-Address': userEoaAddress,
    'X-Chain-Id': chainId
  }
});
const user = await response.json();

const virtualCardCapability = user.capabilities.find(c => c.type === 'VisaVirtualCard');
if (virtualCardCapability?.status === 'Active') {
  // User can issue virtual cards
}
import requests

response = requests.get(
    f"{base_url}/api/v2/user",
    headers={
        "Authorization": f"Bearer {access_token}",
        "X-User-Address": user_eoa_address,
        "X-Chain-Id": chain_id
    }
)
user = response.json()

virtual_card = next((c for c in user["capabilities"] if c["type"] == "VisaVirtualCard"), None)
if virtual_card and virtual_card["status"] == "Active":
    # User can issue virtual cards
    pass
req, _ := http.NewRequest("GET", baseURL+"/api/v2/user", nil)
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("X-User-Address", userEoaAddress)
req.Header.Set("X-Chain-Id", chainId)

resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()

var user UserResponse
json.NewDecoder(resp.Body).Decode(&user)

for _, cap := range user.Capabilities {
    if cap.Type == "VisaVirtualCard" && cap.Status == "Active" {
        // User can issue virtual cards
    }
}

Response (relevant fields):

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "owner": "0x1234567890abcdef1234567890abcdef12345678",
  "verification": {
    "status": "Approved",
    "levels": [
      {
        "status": "Approved",
        "type": "SDD",
        "name": "Wirex SDD Level"
      }
    ]
  },
  "capabilities": [
    {
      "type": "VisaVirtualCard",
      "verification_requirements": [
        { "type": "BDD", "order": 1 }
      ],
      "prerequisites": null,
      "status": "Active"
    },
    {
      "type": "VisaPlasticCard",
      "verification_requirements": [
        { "type": "SDD", "order": 1 }
      ],
      "prerequisites": null,
      "status": "Active"
    }
  ]
}

Card Capabilities

CapabilityDescription
VisaVirtualCardIssue virtual Visa cards
VisaPlasticCardIssue physical Visa cards

Capability Fields

FieldDescription
typeCapability identifier
statusCurrent status (see below)
status_reasonExplanation when status is not Active
verification_requirementsKYC levels required for this capability
prerequisitesOther capabilities that must be active first

Capability Status

StatusDescriptionAction
ActiveReady to issue cardsProceed with issuance
NotFulfilledRequirements not metCheck status_reason for details
NotAvailableNot available for user/regionCannot issue this card type

Example: NotFulfilled Due to Verification Level

{
  "type": "VisaPlasticCard",
  "verification_requirements": [
    { "type": "SDD", "order": 1 }
  ],
  "prerequisites": null,
  "status": "NotFulfilled",
  "status_reason": "Verification level SDD is required"
}

The user has completed BDD verification but VisaPlasticCard requires SDD level. The user must complete SDD verification before ordering a plastic card


Issue Virtual Card

Virtual card issuance may require fee payment depending on your company configuration. Follow these steps:

Step 1: Check Order Fees

GET /api/v1/cards/Virtual/fees/{country}
ParameterDescription
countryUser's country (ISO 3166-1 alpha-2, e.g., GB)

Response:

{
  "currency": "USD",
  "order_fee": 9.99,
  "estimated_payment_amounts": [
    {
      "amount": 10.05,
      "precise_amount": "10050000",
      "token_address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
      "token_symbol": "USDC",
      "rate": 0.9940
    }
  ]
}
FieldDescription
order_feeFee amount in fiat currency
estimated_payment_amountsFee converted to supported tokens

If the endpoint returns a 400 error with "Card fees are not enforced", fees are not applicable for your company — skip to Step 3.

const feesResponse = await fetch(`${baseUrl}/api/v1/cards/Virtual/fees/${country}`, {
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'X-User-Address': userEoaAddress,
    'X-Chain-Id': chainId
  }
});

if (feesResponse.status === 400) {
  // Fees not enforced, skip to issuing card
} else {
  const fees = await feesResponse.json();
  // Proceed to create fees invoice
}
response = requests.get(
    f"{base_url}/api/v1/cards/Virtual/fees/{country}",
    headers={
        "Authorization": f"Bearer {access_token}",
        "X-User-Address": user_eoa_address,
        "X-Chain-Id": chain_id
    }
)

if response.status_code == 400:
    # Fees not enforced, skip to issuing card
    pass
else:
    fees = response.json()
    # Proceed to create fees invoice
req, _ := http.NewRequest("GET", baseURL+"/api/v1/cards/Virtual/fees/"+country, nil)
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("X-User-Address", userEoaAddress)
req.Header.Set("X-Chain-Id", chainId)

resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()

if resp.StatusCode == 400 {
    // Fees not enforced, skip to issuing card
} else {
    var fees FeesResponse
    json.NewDecoder(resp.Body).Decode(&fees)
    // Proceed to create fees invoice
}

Step 2: Create Fees Invoice (If Required)

POST /api/v2/cards/Virtual/fees/{country}/payment

Request:

{
  "token_address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
}
FieldTypeRequiredDescription
token_addressstringYesToken to pay fees with

Response:

{
  "delivery_id": 12345,
  "recipient_address": "0x...",
  "payment_amount": {
    "amount": 10.05,
    "precise_amount": "10050000",
    "token_address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
    "token_symbol": "USDC",
    "rate": 0.9940
  }
}

The fee is deducted from the user's unified balance automatically when issuing the card.

const invoiceResponse = await fetch(`${baseUrl}/api/v2/cards/Virtual/fees/${country}/payment`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'X-User-Address': userEoaAddress,
    'X-Chain-Id': chainId,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    token_address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'
  })
});
const invoice = await invoiceResponse.json();
const deliveryId = invoice.delivery_id;
response = requests.post(
    f"{base_url}/api/v2/cards/Virtual/fees/{country}/payment",
    headers={
        "Authorization": f"Bearer {access_token}",
        "X-User-Address": user_eoa_address,
        "X-Chain-Id": chain_id,
        "Content-Type": "application/json"
    },
    json={"token_address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"}
)
invoice = response.json()
delivery_id = invoice["delivery_id"]
body, _ := json.Marshal(map[string]string{
    "token_address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
})
req, _ := http.NewRequest("POST", baseURL+"/api/v2/cards/Virtual/fees/"+country+"/payment", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("X-User-Address", userEoaAddress)
req.Header.Set("X-Chain-Id", chainId)
req.Header.Set("Content-Type", "application/json")

resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()

var invoice InvoiceResponse
json.NewDecoder(resp.Body).Decode(&invoice)
deliveryId := invoice.DeliveryId

Step 3: Issue Card

POST /api/v2/cards/virtual

Headers (S2S example):

  • Authorization: Bearer {access_token}
  • X-User-Address: {user_eoa_address}
  • X-Chain-Id: {chain_id}

For other authentication methods, see Authentication.

Request:

{
  "delivery_id": 12345,
  "card_name": "My Shopping Card",
  "name_on_card": "Alex Grey"
}
FieldTypeRequiredDescription
delivery_idintegerConditionalRequired if fees were created in Step 2
card_namestringNoDisplay name for the card (1-50 chars)
name_on_cardstringNoName printed on card (2-26 chars, letters only)

Response:

{
  "id": "64120850-73a1-4df5-a074-d463258c9deb"
}

The card is created in Requested status and transitions to NotActivated once processed.

const issueResponse = await fetch(`${baseUrl}/api/v2/cards/virtual`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'X-User-Address': userEoaAddress,
    'X-Chain-Id': chainId,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    delivery_id: deliveryId, // omit if fees not enforced
    card_name: 'My Shopping Card',
    name_on_card: 'Alex Grey'
  })
});
const card = await issueResponse.json();
console.log('Card ID:', card.id);
response = requests.post(
    f"{base_url}/api/v2/cards/virtual",
    headers={
        "Authorization": f"Bearer {access_token}",
        "X-User-Address": user_eoa_address,
        "X-Chain-Id": chain_id,
        "Content-Type": "application/json"
    },
    json={
        "delivery_id": delivery_id,  # omit if fees not enforced
        "card_name": "My Shopping Card",
        "name_on_card": "Alex Grey"
    }
)
card = response.json()
print("Card ID:", card["id"])
body, _ := json.Marshal(map[string]interface{}{
    "delivery_id":  deliveryId, // omit if fees not enforced
    "card_name":    "My Shopping Card",
    "name_on_card": "Alex Grey",
})
req, _ := http.NewRequest("POST", baseURL+"/api/v2/cards/virtual", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("X-User-Address", userEoaAddress)
req.Header.Set("X-Chain-Id", chainId)
req.Header.Set("Content-Type", "application/json")

resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()

var card CardResponse
json.NewDecoder(resp.Body).Decode(&card)
fmt.Println("Card ID:", card.Id)

Issue Physical Card

Physical card issuance requires checking delivery availability and selecting a delivery method.

Step 1: Get Delivery Countries

GET /api/v1/cards/delivery/countries

Response:

["GB", "DE", "FR", "ES", "IT"]
const countriesResponse = await fetch(`${baseUrl}/api/v1/cards/delivery/countries`, {
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'X-User-Address': userEoaAddress,
    'X-Chain-Id': chainId
  }
});
const countries = await countriesResponse.json();
response = requests.get(
    f"{base_url}/api/v1/cards/delivery/countries",
    headers={
        "Authorization": f"Bearer {access_token}",
        "X-User-Address": user_eoa_address,
        "X-Chain-Id": chain_id
    }
)
countries = response.json()
req, _ := http.NewRequest("GET", baseURL+"/api/v1/cards/delivery/countries", nil)
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("X-User-Address", userEoaAddress)
req.Header.Set("X-Chain-Id", chainId)

resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()

var countries []string
json.NewDecoder(resp.Body).Decode(&countries)

Step 2: Get Delivery Methods

GET /api/v1/cards/delivery/methods/{country}
ParameterDescription
countryISO 3166-1 alpha-2 code from Step 1

Response:

[
  {
    "fee": 15.00,
    "currency": "USD",
    "provider": "DHL",
    "estimated_payment_amounts": [
      {
        "amount": 15.10,
        "precise_amount": "15100000",
        "token_address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
        "token_symbol": "USDC",
        "rate": 0.9934
      }
    ]
  }
]
FieldDescription
feeDelivery fee in fiat currency
currencyFiat currency for the fee
providerCourier provider (use in Step 4 and Step 5)
estimated_payment_amountsFee converted to supported tokens

Display available methods to the user and use the selected provider value in Step 4 and Step 5.

const methodsResponse = await fetch(`${baseUrl}/api/v1/cards/delivery/methods/${country}`, {
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'X-User-Address': userEoaAddress,
    'X-Chain-Id': chainId
  }
});
const methods = await methodsResponse.json();
const selectedProvider = methods[0].provider; // e.g., 'DHL'
response = requests.get(
    f"{base_url}/api/v1/cards/delivery/methods/{country}",
    headers={
        "Authorization": f"Bearer {access_token}",
        "X-User-Address": user_eoa_address,
        "X-Chain-Id": chain_id
    }
)
methods = response.json()
selected_provider = methods[0]["provider"]  # e.g., 'DHL'
req, _ := http.NewRequest("GET", baseURL+"/api/v1/cards/delivery/methods/"+country, nil)
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("X-User-Address", userEoaAddress)
req.Header.Set("X-Chain-Id", chainId)

resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()

var methods []DeliveryMethod
json.NewDecoder(resp.Body).Decode(&methods)
selectedProvider := methods[0].Provider // e.g., "DHL"

Step 3: Check Order Fees

GET /api/v1/cards/Plastic/fees/{country}

See Issue Virtual Card for response format.

Step 4: Create Fees Invoice (If Required)

POST /api/v2/cards/Plastic/fees/{country}/payment

Request:

{
  "token_address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
  "delivery_provider": "DHL"
}
FieldTypeRequiredDescription
token_addressstringYesToken to pay fees with
delivery_providerstringYesProvider from Step 2

Step 5: Issue Card

POST /api/v2/cards/plastic

Request:

{
  "delivery_id": 12345,
  "delivery_provider": "DHL",
  "card_name": "My Travel Card",
  "name_on_card": "Alex Grey",
  "delivery_address": {
    "line1": "10 Downing Street",
    "line2": "Flat 2",
    "city": "London",
    "state": "",
    "zip_code": "SW1A 2AA",
    "country": "GB"
  }
}
FieldTypeRequiredDescription
delivery_idintegerConditionalRequired if fees were created in Step 4
delivery_providerstringNoCourier provider from Step 2
card_namestringNoDisplay name (1-50 chars)
name_on_cardstringNoName printed on card (2-26 chars)
delivery_addressobjectYesShipping address

Delivery Address Fields

FieldTypeRequiredDescription
line1stringYesStreet address
line2stringNoApartment, suite
citystringYesCity name
statestringNoState or province
zip_codestringYesPostal/ZIP code
countrystringYesISO 3166-1 alpha-2 code

Response:

{
  "id": "64120850-73a1-4df5-a074-d463258c9deb"
}
const issueResponse = await fetch(`${baseUrl}/api/v2/cards/plastic`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'X-User-Address': userEoaAddress,
    'X-Chain-Id': chainId,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    delivery_id: deliveryId,
    delivery_provider: 'DHL',
    card_name: 'My Travel Card',
    name_on_card: 'Alex Grey',
    delivery_address: {
      line1: '10 Downing Street',
      line2: 'Flat 2',
      city: 'London',
      state: '',
      zip_code: 'SW1A 2AA',
      country: 'GB'
    }
  })
});
const card = await issueResponse.json();
response = requests.post(
    f"{base_url}/api/v2/cards/plastic",
    headers={
        "Authorization": f"Bearer {access_token}",
        "X-User-Address": user_eoa_address,
        "X-Chain-Id": chain_id,
        "Content-Type": "application/json"
    },
    json={
        "delivery_id": delivery_id,
        "delivery_provider": "DHL",
        "card_name": "My Travel Card",
        "name_on_card": "Alex Grey",
        "delivery_address": {
            "line1": "10 Downing Street",
            "line2": "Flat 2",
            "city": "London",
            "state": "",
            "zip_code": "SW1A 2AA",
            "country": "GB"
        }
    }
)
card = response.json()
body, _ := json.Marshal(map[string]interface{}{
    "delivery_id":       deliveryId,
    "delivery_provider": "DHL",
    "card_name":         "My Travel Card",
    "name_on_card":      "Alex Grey",
    "delivery_address": map[string]string{
        "line1":    "10 Downing Street",
        "line2":    "Flat 2",
        "city":     "London",
        "state":    "",
        "zip_code": "SW1A 2AA",
        "country":  "GB",
    },
})
req, _ := http.NewRequest("POST", baseURL+"/api/v2/cards/plastic", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("X-User-Address", userEoaAddress)
req.Header.Set("X-Chain-Id", chainId)
req.Header.Set("Content-Type", "application/json")

resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()

var card CardResponse
json.NewDecoder(resp.Body).Decode(&card)

Card Lifecycle

Requested → NotActivated → Active
                             ↓
                          Blocked ←→ Active
                             ↓
                           Closed
StatusDescription
RequestedCard order submitted, processing
NotActivatedCard issued, awaiting activation
ActiveCard is active and usable
BlockedTemporarily blocked (can be unblocked)
ClosedPermanently closed

Activate Card

Activation is required only for physical cards. This protects user funds if the card is stolen during shipment.

PUT /api/v1/cards/{cardId}/activate

Request:

{
  "card_number_last4": "1234",
  "expiry_date": "12/2027"
}
FieldTypeRequiredDescription
card_number_last4stringYesLast 4 digits of card number
expiry_datestringYesExpiration date in MM/YYYY format

Response: Empty on success (200 OK)

await fetch(`${baseUrl}/api/v1/cards/${cardId}/activate`, {
  method: 'PUT',
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'X-User-Address': userEoaAddress,
    'X-Chain-Id': chainId,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    card_number_last4: '1234',
    expiry_date: '12/2027'
  })
});
requests.put(
    f"{base_url}/api/v1/cards/{card_id}/activate",
    headers={
        "Authorization": f"Bearer {access_token}",
        "X-User-Address": user_eoa_address,
        "X-Chain-Id": chain_id,
        "Content-Type": "application/json"
    },
    json={
        "card_number_last4": "1234",
        "expiry_date": "12/2027"
    }
)
body, _ := json.Marshal(map[string]string{
    "card_number_last4": "1234",
    "expiry_date":       "12/2027",
})
req, _ := http.NewRequest("PUT", baseURL+"/api/v1/cards/"+cardId+"/activate", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("X-User-Address", userEoaAddress)
req.Header.Set("X-Chain-Id", chainId)
req.Header.Set("Content-Type", "application/json")

http.DefaultClient.Do(req)

Get Cards

Retrieve all cards for a user.

GET /api/v1/cards
const response = await fetch(`${baseUrl}/api/v1/cards`, {
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'X-User-Address': userEoaAddress,
    'X-Chain-Id': chainId
  }
});
const { data: cards } = await response.json();
response = requests.get(
    f"{base_url}/api/v1/cards",
    headers={
        "Authorization": f"Bearer {access_token}",
        "X-User-Address": user_eoa_address,
        "X-Chain-Id": chain_id
    }
)
cards = response.json()["data"]
req, _ := http.NewRequest("GET", baseURL+"/api/v1/cards", nil)
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("X-User-Address", userEoaAddress)
req.Header.Set("X-Chain-Id", chainId)

resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()

var result CardsResponse
json.NewDecoder(resp.Body).Decode(&result)
cards := result.Data

Response:

{
  "data": [
    {
      "id": "64120850-73a1-4df5-a074-d463258c9deb",
      "card_wallet_address": "0xAAFF0821A09A1Aac28B72dD3Ff410A7ea5FEb874",
      "status": "Active",
      "status_reason": "",
      "previous_status": "NotActivated",
      "generation": "Gen2",
      "provider": "Wirex",
      "limit": {
        "daily_limit": 10000.00,
        "daily_usage": 250.00,
        "currency": "USD"
      },
      "allowed_actions": [
        { "type": "Block", "relative_path": "/api/v1/cards/:cardId/block" },
        { "type": "Close", "relative_path": "/api/v1/cards/:cardId/close" },
        { "type": "SetLimit", "relative_path": "/api/v1/cards/:cardId/limit" },
        { "type": "GetDetails", "relative_path": "/api/v1/cards/:cardId/details" },
        { "type": "GetCvv", "relative_path": "/api/v1/cards/:cardId/cvv" }
      ],
      "card_data": {
        "card_name": "My Card",
        "name_on_card": "Alex Grey",
        "card_number_last_4": "0333",
        "expiry_date": "12/2027",
        "payment_system": "Visa",
        "format": "Virtual"
      },
      "created_at": "2024-01-01T10:00:00Z",
      "updated_at": "2024-01-01T10:05:00Z"
    }
  ]
}

Allowed Action Types

TypeDescription
ActivateActivate the card
BlockBlock the card
UnblockUnblock the card
CloseClose the card
SetLimitChange card limits
GetDetailsGet full card details (PAN)
GetCvvGet card CVV
GetPinGet card PIN

Card Data Visibility

FieldVisible When
card_number_first_4Status is Requested or NotActivated
card_number_last_4Status is Active, Blocked, or Closed
expiry_dateStatus is Active, Blocked, or Closed

Manage Cards

Block Card

Temporarily block a card. Can be unblocked later.

PUT /api/v1/cards/{cardId}/block

Unblock Card

Restore a blocked card to active status.

PUT /api/v1/cards/{cardId}/unblock

Close Card

Permanently close a card. This action cannot be undone.

PUT /api/v1/cards/{cardId}/close
// Block
await fetch(`${baseUrl}/api/v1/cards/${cardId}/block`, {
  method: 'PUT',
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'X-User-Address': userEoaAddress,
    'X-Chain-Id': chainId
  }
});

// Unblock
await fetch(`${baseUrl}/api/v1/cards/${cardId}/unblock`, {
  method: 'PUT',
  headers: { /* same headers */ }
});

// Close
await fetch(`${baseUrl}/api/v1/cards/${cardId}/close`, {
  method: 'PUT',
  headers: { /* same headers */ }
});
headers = {
    "Authorization": f"Bearer {access_token}",
    "X-User-Address": user_eoa_address,
    "X-Chain-Id": chain_id
}

# Block
requests.put(f"{base_url}/api/v1/cards/{card_id}/block", headers=headers)

# Unblock
requests.put(f"{base_url}/api/v1/cards/{card_id}/unblock", headers=headers)

# Close
requests.put(f"{base_url}/api/v1/cards/{card_id}/close", headers=headers)
actions := []string{"block", "unblock", "close"}
for _, action := range actions {
    req, _ := http.NewRequest("PUT", baseURL+"/api/v1/cards/"+cardId+"/"+action, nil)
    req.Header.Set("Authorization", "Bearer "+accessToken)
    req.Header.Set("X-User-Address", userEoaAddress)
    req.Header.Set("X-Chain-Id", chainId)
    http.DefaultClient.Do(req)
}

Webhooks

Card status changes trigger webhook notifications. The payload structure matches the Get Cards response.

Card Status Webhook

Endpoint: POST {your_webhook_base_url}/v2/webhooks/cards

The payload structure matches the Get Cards response item.

{
  "id": "64120850-73a1-4df5-a074-d463258c9deb",
  "card_wallet_address": "0xAAFF0821A09A1Aac28B72dD3Ff410A7ea5FEb874",
  "status": "Active",
  "status_reason": "",
  "previous_status": "NotActivated",
  "generation": "Gen2",
  "provider": "Wirex",
  "limit": {
    "daily_limit": 10000.00,
    "daily_usage": 0.00,
    "currency": "USD"
  },
  "allowed_actions": [
    { "type": "Block", "relative_path": "/api/v1/cards/:cardId/block" },
    { "type": "Close", "relative_path": "/api/v1/cards/:cardId/close" }
  ],
  "card_data": {
    "card_name": "My Card",
    "name_on_card": "Alex Grey",
    "card_number_last_4": "0333",
    "expiry_date": "12/2027",
    "payment_system": "Visa",
    "format": "Virtual"
  },
  "created_at": "2024-01-01T10:00:00Z",
  "updated_at": "2024-01-01T10:05:00Z"
}

Card Limit Webhook

Endpoint: POST {your_webhook_base_url}/v2/webhooks/card-limits

{
  "card_id": "64120850-73a1-4df5-a074-d463258c9deb",
  "transaction_limit": 500.00,
  "daily_limit": 1000.00,
  "daily_usage": 200.00,
  "monthly_limit": 5000.00,
  "monthly_usage": 1500.00,
  "lifetime_limit": 100000.00,
  "lifetime_usage": 25000.00
}

Field Validation

Card Name

Pattern: ^[a-zA-Z0-9\-':+# @]{1,50}$

Name on Card

Pattern: ^[A-Za-z][A-Za-z .'-]{1,25}$

  • Must start with a letter
  • Only letters, spaces, periods, apostrophes, and hyphens
  • 2-26 characters total

Delivery Address

FieldPattern
line1^[A-Za-z0-9&.,''\-/() :+#]{2,100}$
line2^[A-Za-z0-9&.,''\-/() :+#]{2,100}$
city^[A-Za-z\s\-'.]{2,100}$
state^[A-Za-z][A-Za-z\s\-']{1,99}$
zip_code^[A-Za-z0-9\s\-]{3,12}$
country^[A-Z]{2}$ (ISO 3166-1 alpha-2)

Error Handling

All errors return JSON with the following structure:

{
  "error_reason": "ErrorInvalidField",
  "error_description": "Human-readable message",
  "error_category": {
    "category": "CategoryValidationFailure",
    "http_status_code": 400
  },
  "error_details": [
    { "key": "field", "details": "field_name" }
  ]
}

Validation Errors (400)

Missing Required Field

{
  "error_reason": "ErrorMissingField",
  "error_description": "delivery_address is required",
  "error_category": {
    "category": "CategoryValidationFailure",
    "http_status_code": 400
  },
  "error_details": [
    { "key": "field", "details": "delivery_address" },
    { "key": "issue", "details": "missing" }
  ]
}

Invalid Field Format

{
  "error_reason": "ErrorInvalidField",
  "error_description": "Invalid format for card number last4",
  "error_category": {
    "category": "CategoryValidationFailure",
    "http_status_code": 400
  },
  "error_details": [
    { "key": "field", "details": "card_number_last4" },
    { "key": "issue", "details": "invalid_format" },
    { "key": "pattern", "details": "^\\d{4}$" }
  ]
}

Invalid Card Status

Returned when attempting an action not allowed for the card's current status.

{
  "error_reason": "ErrorInvalidStatus",
  "error_description": "Card is not in valid status for this action",
  "error_category": {
    "category": "CategoryValidationFailure",
    "http_status_code": 400
  },
  "error_details": [
    { "key": "field", "details": "card_status" },
    { "key": "value", "details": "Requested" }
  ]
}
ActionInvalid Statuses
ActivateRequested, Active, Blocked, Closed
BlockNotActivated, Requested, Closed, Blocked
UnblockAny status except Blocked
CloseNotActivated, Requested, Closed

Delivery ID Required

When fees are enforced and delivery_id is missing or invalid.

{
  "error_reason": "ErrorGeneral",
  "error_description": "Delivery ID is required and must be positive",
  "error_category": {
    "category": "CategoryValidationFailure",
    "http_status_code": 400
  },
  "error_details": [
    { "key": "field", "details": "delivery_id" }
  ]
}

Capability Not Active

{
  "error_reason": "ErrorGeneral",
  "error_description": "Virtual card capability is not active",
  "error_category": {
    "category": "CategoryInternalFailure",
    "http_status_code": 500
  }
}

Not Found Errors (400)

Delivery Countries/Methods Not Found

{
  "error_reason": "ErrorNotFound",
  "error_description": "No delivery methods found for country",
  "error_category": {
    "category": "CategoryValidationFailure",
    "http_status_code": 400
  },
  "error_details": [
    { "key": "field", "details": "country" },
    { "key": "value", "details": "XX" }
  ]
}

Fees Not Found

{
  "error_reason": "ErrorNotFound",
  "error_description": "No fees found for specified country and method",
  "error_category": {
    "category": "CategoryValidationFailure",
    "http_status_code": 400
  },
  "error_details": [
    { "key": "fields", "details": "type,country" },
    { "key": "type", "details": "Virtual" },
    { "key": "country", "details": "XX" }
  ]
}

Fees Not Enforced

Returned when calling fees endpoints but fees are not configured for your company.

{
  "error_reason": "ErrorGeneral",
  "error_description": "Request validation failed",
  "error_category": {
    "category": "CategoryValidationFailure",
    "http_status_code": 400
  },
  "error_details": [
    { "key": "request", "details": "Card fees are not enforced" }
  ]
}

Server Errors (500)

Error DescriptionCause
Failed to get cardCard not found or service unavailable
Failed to issue virtual cardCard issuer service error
Failed to issue plastic cardCard issuer service error
Failed to activate cardCard issuer service error
Failed to process delivery paymentPayment processing error