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.
| Field | Required | Description |
|---|---|---|
| ID | Output | Wonder-generated unique card ID. |
| Reference_id | Output | Card reference identifier, assigned at provisioning. |
| Title | Output | Cardholder display name. |
| Business Name | Output | Your Wonder business name. Returned by link cardAPI. |
| Type | Output | Expense or Personal |
| Status | Output | Current card availability state (see card statusDOC below). |
| physical_status | Output | Physical card lifecycle state (see physical card statusDOC below). |
| Settlement_currency | Output | Currency in which transactions settle (e.g.HKD , USD). |
| First_6_digits | Output | First 6 digits of the card number (BIN). |
| Last_4_digits | Output | Last 4 digits of the card number. |
| Pan | Output | Masked card number (e.g. 356772******8348). Full PAN requires ECDSA exchange. |
| Is_encrypt | Output | Whether the PAN is encrypted (true for all cards). Returned by Link Card. |
| Issuer_brand | Output | Card network (currently JCB). |
| Expiration_date | Output | Expiration date for card(currently MM/YY format). |
| P_user_id | Output | Only for Consumer cards - the linked user ID. |
| Limites | Output | Array 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.
| Status | Description | Can transact |
|---|---|---|
| Active | Virtual card can be used normally | Yes |
| Suspended | Suspended by the system (e.g. fraud or compliance reason). | No |
| Locked | Temporarily disabled via lock APIAPI , and you also can use unlocked APIAPI to activate the card again. | No |
| Terminated | Permanently 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.
| Status | Description | Can transact |
|---|---|---|
| Idle | Virtual card provisioned but not yet activated or ordered as physical. | No |
| Ordered | Physical card order placed; not yet activated with PIN. | No |
| Active | Physical card activated and in normal use. | Yes |
| Suspended | Suspended by the system (e.g. fraud or compliance). | No |
| Locked | Associated virtual card status has been changed to Locked. | No |
| Terminated | Associated 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".
| Period | Description |
|---|---|
| Transaction | Max per single transaction. |
| Daily | Max cumulative spend per calendar day. Resets at 00:00 daily. |
| Monthly | Max cumulative spend per calendar month (1st to last day, based on actual month length). Resets on the 1st of each month. |
| Lifetime | All limits are enforced simultaneously. A transaction is declined if it exceeds any of the remaining limits. |
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.
Permanently close a card. Unlike Lock, please note termination is irreversible. The card can never be used again.
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.
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.
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.
| Field | Required | Description |
|---|---|---|
| Reference_id | Yes | Your system's unique user ID. Duplicate references are rejected. |
| Calling_code | Yes | Country calling code (1–3 digits, e.g. 852). |
| Phone_number | Yes | Phone number (1–11 digits). Must be unique across all users. |
| Document_type | Yes | PASSPORT,DRIVER_LICENSE or ID_CARD |
| Document_number | Yes | eKYC document number. Must be accurate for compliance. |
| first_name | No | At least one of first_name or last_name is required. |
| last_name | No | At least one of first_name or last_name is required. |
| Date_of_birth | Yes | ISO date format (YYYY-MM-DD). |
| Issuing_country | Yes | ISO 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.
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.
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.
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 .
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.
- Dependencies and constants
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"
)- Step 1: Generate ECDSA key pair and kid
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
}- Step 2: Call exchange key API
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
}- Step 3: Derive secretKey via ECDH and SHA-256
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]
}- Step 4: Encrypt PIN via JWE and call activation API
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
}- Main workflow integration
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.
| Field | Required |
|---|---|
| Authorization | A new transaction is authorized on a card. |
| Reversed | An authorization is voided from the acquirer side (before settlement). |
| Settled | An 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.
Call without p_user_id to get your corporate wallet balance per currency. This is the shared pool used by all Business type cards.
Pass p_user_id to query a specific cardholder's wallet balance. Each cardholder may have balances in multiple currencies depending on top-ups.
Related APIs
The transactions that this card make.
Edit the information for this consumer cardholder by p_user_id, such as phone.
Query the consumer cardholder information.