Wonder

Card Issuing

Issue multi-currency JCB cards at scale. Cards can be used for local transactions and international payments at ultra-low rates. The Issuing API lets you manage card statuses, set spending limits, activate physical cards, and track every transaction in real time via webhooks.

Card management

Everything about the card itself — types, data model, lifecycle statuses, spending controls, and operations you can perform on individual cards.

Workflow

1 Select the card typeDOCthat you want to integrate with and contact with our Wonder support@wonder.app.

2 Choose if you need virtual card or physical card or both.

3 To enable card spend, you need to add fundDOC .

4 (Optional)Set up spending limitDOC, if you dont set, then the card limit is 100,000,000,000.00HKD.

5 Simulate the card transaction via card simulator.

Card types

Cards are provisioned in bulk by the Wonder team — there is no API to create cards. Once delivered, you manage them through the cards API. Two card models are available depending on how funds are structured.

Business card

Shares funds directly from your corporate Wonder account. No separate user management or eKYC required — the card draws from the business wallet. Ideal for employee spending cards, procurement, and company-wide card programs.

Consumer card

Each cardholder gets their own independent balance with isolated funds. Requires creating a userAPI (with eKYC) and linking a cardAPI exactly one card per user. Funds are topped up via the consumer add fund processDOC. Ideal for consumer-facing card products.

Card model

A card is identified by its UUID and card_reference_id. Sensitive data (full PAN) is encrypted and requires an ECDSA exchange keyDOC to get full card numberAPI.

FieldRequiredDescription
IDOutputWonder-generated unique card ID.
Reference_idOutputCard reference identifier, assigned at provisioning.
TitleOutputCardholder display name.
Business NameOutputYour Wonder business name. Returned by link cardAPI.
TypeOutputExpense or Personal
StatusOutputCurrent card availability state (see card statusDOC below).
physical_statusOutputPhysical card lifecycle state (see physical card statusDOC below).
Settlement_currencyOutputCurrency in which transactions settle (e.g.HKD , USD).
First_6_digitsOutputFirst 6 digits of the card number (BIN).
Last_4_digitsOutputLast 4 digits of the card number.
PanOutputMasked card number (e.g. 356772******8348). Full PAN requires ECDSA exchange.
Is_encryptOutputWhether the PAN is encrypted (true for all cards). Returned by Link Card.
Issuer_brandOutputCard network (currently JCB).
Expiration_dateOutputExpiration date for card(currently MM/YY format).
P_user_idOutputOnly for Consumer cards - the linked user ID.
LimitesOutputArray of limit rules currently applied to the card (see card limitsDOC below).

Statuses

Card.status: The status for virtual card. Please be noted, if change the status field to Suspended, Locked or Terminated, the associated physical card status also will be changed accordingly.

StatusDescriptionCan transact
ActiveVirtual card can be used normallyYes
SuspendedSuspended by the system (e.g. fraud or compliance reason).No
LockedTemporarily disabled via lock APIAPI , and you also can use unlocked APIAPI to activate the card again.No
TerminatedPermanently closed via terminated APIAPI. Irreversible.No

Card.physical_status: The status for physical card, only when you create the virtual card, then can apply the physical card for that virtual card. Please be noted the physical_status change wont affect the associated virtual card status.

StatusDescriptionCan transact
IdleVirtual card provisioned but not yet activated or ordered as physical.No
OrderedPhysical card order placed; not yet activated with PIN.No
ActivePhysical card activated and in normal use.Yes
SuspendedSuspended by the system (e.g. fraud or compliance).No
LockedAssociated virtual card status has been changed to Locked.No
TerminatedAssociated virtual card status has been changed to Terminated.No

Card limits

Four independent limit rules control spending across different time windows(see edit card limitAPI). All four can be active simultaneously. To disable a rule, set its limit_amount to "0".

PeriodDescription
TransactionMax per single transaction.
DailyMax cumulative spend per calendar day. Resets at 00:00 daily.
MonthlyMax cumulative spend per calendar month (1st to last day, based on actual month length). Resets on the 1st of each month.
LifetimeAll limits are enforced simultaneously. A transaction is declined if it exceeds any of the remaining limits.
INFO

Limits are enforced at the authorization stage. If a settlement amount differs from the authorized amount, the actual usage may temporarily exceed the limit. Set limits with a buffer for such variances.

Card operations

Manage individual cards throughout their lifecycle.

Query card listsAPI
Query a cardAPI

List all cards or get a single card by ID. Each card includes its limit rules, status, and (for Consumer cards) linked user ID. Paginated via offset/limit.

Lock a cardAPI
Unlock a cardAPI

Temporarily disable a card (Lock) or restore it (Unlock). Locked cards reject all transactions. Locking is reversible; Terminate is not.

Terminate a cardAPI

Permanently close a card. Unlike Lock, please note termination is irreversible. The card can never be used again.

Edit limitsAPI

Update spending limits for all four periods in a single call. Pass each limit rule with its ID, new amount, and desired status. Use the Get Card Limit Histories APIAPI to audit changes.

Get full card numberAPI

Retrieve the full unmasked PAN. This is sensitive and requires an ECDSA key exchange session需加 before calling. The flow is identical to physical card activation.

Physical card activation

Virtual cards are active immediately after provisioning. Physical cards require a PIN to be set through a secure ECDSA key exchange session.

1 Generate keys using the ECDSA algorithm. 2 Call ECDSA key exchange session to exchange keys. 3 Calculate and obtain the secretkey using the returned key. 4 Encrypt the password with the secretkey and call Set card pin and activeAPI.

INFO

The same ECDSA key exchange session is also used for the Get full card numberAPI — generate a key pair, exchange keys, then retrieve the encrypted full PAN.

Supproting services

Cardholder identity, transaction verification, real-time notifications, and balance management — everything that sits around the card itself.

Consumer cardholders

Consumer cards require a cardholder for identity management. Each cardholder can hold exactly one card.

FieldRequiredDescription
Reference_idYesYour system's unique user ID. Duplicate references are rejected.
Calling_codeYesCountry calling code (1–3 digits, e.g. 852).
Phone_numberYesPhone number (1–11 digits). Must be unique across all users.
Document_typeYesPASSPORT,DRIVER_LICENSE or ID_CARD
Document_numberYeseKYC document number. Must be accurate for compliance.
first_nameNoAt least one of first_name or last_name is required.
last_nameNoAt least one of first_name or last_name is required.
Date_of_birthYesISO date format (YYYY-MM-DD).
Issuing_countryYesISO Alpha-3 country code (e.g. HKG,USA).

Link card to a consumer cardholderAPI

After create consumer cardholderAPI, call the link card APIAPI with the card_reference_id and p_user_id to assign one card from the unassigned Personal card pool. One card per cardholder. The linked card inherits the cardholder's identity for compliance and reporting.

Platform transfer fund to another consumer cardholderAPI

Transfer funds between user accounts instantly.
Conditions: eKYC must match registration, sender must have sufficient balance, and both consumer cardholder must be Active. Transfers execute immediately with no settlement delay.

3D secure verification

3D Secure (3DS) adds a verification step during online card payments, shifting fraud liability to the issuer. Wonder sends the OTP to your server via webhookDOC — you choose how to deliver it to your cardholder(both business and consumer).

Add fund

You need to add fund to your account, then make transaction. The process for business card and consumer cand is differenct, please see below:

Business type wallet top up: To top up in Wonder dashboard, see How to Add Fund to your Global Account

Consumer type wallet top up: Consumer cards have independent balances that start at zero. Cardholders add funds by sending crypto (USDT on Solana or Ethereum) to a deposit address generated by the Wallet API. Wonder handles the conversion from crypto to the card's settlement currency automatically. Besides, you can use platform transfer APIAPI to move funds to another consumer card in real time, and can use get platform transfer API listAPI to track every request.

Create walletAPI

Generate a blockchain deposit address for a cardholder. Pick the network (Solana or ETH) and the asset (USDT). Wonder returns a unique wallet address the cardholder sends funds to.

Query walletsAPI
Query a walletAPI

List all wallets for a cardholder or get a single wallet by ID. Each wallet shows its blockchain address, network, asset, and current status. Use this to surface deposit addresses in your app.

Track transactionsAPI

Query the transaction history of any wallet. Each top-up generates a record — use this to confirm deposits and reconcile card balances against incoming crypto transfers.

TIP

Create a wallet for the consumer cardholder → share the deposit address → cardholder sends USDT to the address → Wonder detects the deposit and credits the consumer cardholder's balance in the settlement currency → balance is immediately available for card transactions. Check the real-time balance via the get consumer type balance APIAPI .

WARNING

Conditions for a successful platfomr transfer: both parties must be Active, eKYC details must exactly match what was submitted at registration, and the sender's balance must cover the transfer amount. Transfers are instant — no settlement window.

ECDSA exchange key sample

This example demonstrates the complete process for securely activating a physical card and setting its PIN using ECDSA key exchange and JWE encryption.

  1. Dependencies and constants
go

import(
    "crypto/ecdsa"
    "crypto/elliptic"
    "crypto/rand"
    "crypto/sha256"
    "encoding/hex"
    "encoding/json"
    "fmt"
    "github.com/go-jose/go-jose/v3"
)

const(
    P256Curve = elliptic.P256() // NIST P-256 curve
    KeySize   = 32              // 256 bits for SHA-256 derived key
    ExchangeKeyAPI = "/svc/card_issuer/api/v1/pci_session"
    SetPinActiveAPI = "/api/v1/pci_pin_with_session"
)
  1. Step 1: Generate ECDSA key pair and kid
go

func generateECDSAKeyPair() (*ecdsa.PrivateKey, string, string, string) {
    privateKey, _ := ecdsa.GenerateKey(P256Curve, rand.Reader)
    publicKey := privateKey.Public().(*ecdsa.PublicKey)

    pubKeyX := hex.EncodeToString(publicKey.X.Bytes())
    pubKeyY := hex.EncodeToString(publicKey.Y.Bytes())
    kid := generateUUID() // Generate unique key ID

    return privateKey, pubKeyX, pubKeyY, kid
}
  1. Step 2: Call exchange key API
go
func exchangeKeys(pubKeyX, pubKeyY, kid string) (*jose.JSONWebKey, error) {
    requestPayload := map[string]string{
        "kty": "EC",
        "crv": "P-256",
        "kid": kid,
        "x":   pubKeyX,
        "y":   pubKeyY,
    }
    // Send POST request (pseudo-code)
    // responseBody := httpPost(ExchangeKeyAPI, requestPayload)
    // Parse response and return server's ephemeral public key
    return parseServerEphemPubKey(responseBody), nil
}
  1. Step 3: Derive secretKey via ECDH and SHA-256
go
func deriveSecretKey(clientPriv *ecdsa.PrivateKey, serverEphemPubKey *jose.JSONWebKey, kid string) []byte {
    // Decode server's public key
    x, y := serverEphemPubKey.Key.(*ecdsa.PublicKey).X, serverEphemPubKey.Key.(*ecdsa.PublicKey).Y
    if !P256Curve.IsOnCurve(x, y) {
        panic("Server public key is not on P-256 curve") // Security check
    }

    // Compute ECDH shared secret
    sharedSecret, _ := clientPriv.ECDH(serverEphemPubKey.Key.(*ecdsa.PublicKey))

    // Build KDF combined data
    combinedData := append([]byte{}, kid.Bytes()...)
    combinedData = append(combinedData, sharedSecret...)

    // Apply SHA-256 hash and truncate
    derivedKey := sha256.Sum256(combinedData)
    return derivedKey[:KeySize]
}
  1. Step 4: Encrypt PIN via JWE and call activation API
go
func encryptPINAndActivate(secretKey []byte, kid, pinCode string) error {
    pinData, _ := json.Marshal(map[string]string{"password": pinCode})

    // Configure JWE encrypter (using DIRECT mode)
    encrypter, _ := jose.NewEncrypter(jose.DIRECT, jose.Recipient{Algorithm: jose.DIRECT, Key: secretKey}, nil)

    // Encrypt and build JWE object
    jweObject, _ := encrypter.Encrypt(pinData)
    jweCompact, _ := jweObject.CompactSerialize()

    // Send POST request for activation (pseudo-code)
    // response := httpPost(SetPinActiveAPI, jweCompact)
    fmt.Printf("PIN encrypted successfully, JWE: %s\n", jweCompact)
    return nil
}
  1. Main workflow integration
go
func main() {
    pinCode := "your_pin" // PIN to be set

    // Step 1: Generate key pair
    privateKey, pubKeyX, pubKeyY, kid := generateECDSAKeyPair()

    // Step 2: Exchange keys
    serverEphemPubKey, _ := exchangeKeys(pubKeyX, pubKeyY, kid)

    // Step 3: Derive SecretKey
    secretKey := deriveSecretKey(privateKey, serverEphemPubKey, kid)

    // Step 4: Encrypt PIN and activate
    err := encryptPINAndActivate(secretKey, kid, pinCode)
    if err != nil {
        fmt.Println("Activation failed:", err)
    } else {
        fmt.Println("Card activation workflow completed")
    }
}

Transaction webhook

Real-time transaction webhookDOC are sent to your configured webhook URL for every authorization, reversal, and settlement.

FieldRequired
AuthorizationA new transaction is authorized on a card.
ReversedAn authorization is voided from the acquirer side (before settlement).
SettledAn authorized transaction is settled and funds are captured.

Balance

Query real-time balances for your corporate account or any cardholder account. Balances update immediately as funds move.

Business type balanceAPI

Call without p_user_id to get your corporate wallet balance per currency. This is the shared pool used by all Business type cards.

Consumer type balanceAPI

Pass p_user_id to query a specific cardholder's wallet balance. Each cardholder may have balances in multiple currencies depending on top-ups.

Get card transaction listAPI

The transactions that this card make.

Edit consumer cardholderAPI

Edit the information for this consumer cardholder by p_user_id, such as phone.

Get consumer cardholderAPI

Query the consumer cardholder information.