Card Transactions

How card transactions settle against the Master Account.

Before You Start

Read the following guides before proceeding:

GuideWhy
OverviewUnderstand External Authorization model
Authorization APIHow transaction authorization works
Issue and Manage a CardStandard card issuance flow

Card Issuance

Card issuance follows the same API flow as standard integration. See Issue and Manage a Card.

All card management operations (block, unblock, close, set limits) work identically.


Transaction Settlement

The difference is in how transactions are settled:

AspectStandard FlowExternal Authorization
Balance checkUser's AA walletYour authorization API
Settlement sourceUser's AA walletMaster Account
Balance updatesOn-chain per userYour external system

Standard Flow

1. User pays with card
2. Wirex checks user's on-chain balance
3. Wirex holds funds on user's AA wallet
4. Transaction settles from user's AA wallet
5. Webhook sent

External Authorization Flow

1. User pays with card
2. Wirex calls your authorization API
3. You check balance in your system
4. If approved, Wirex debits Master Account
5. Webhook sent
6. You update user's balance in your system

Transaction Flow Diagram

sequenceDiagram
    participant User
    participant Merchant
    participant Wirex
    participant Partner as Your System
    participant Master as Master Account

    User->>Merchant: Pay with card
    Merchant->>Wirex: Authorization request
    Wirex->>Partner: POST /external-authorization/...
    Partner->>Partner: Check user balance
    Partner-->>Wirex: { is_allowed: true }
    Wirex->>Master: Debit Master Account
    Wirex-->>Merchant: Approved
    Wirex->>Partner: Transaction webhook
    Partner->>Partner: Debit user balance

Webhooks

After transaction completion, Wirex sends a webhook notification. Use this to update your internal balance records.

Card Transaction Webhook

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

{
  "id": "a7b2a4b3-2f61-41a7-9f5a-3f3f0b1a3d22",
  "type": "CardTransaction",
  "status": "Completed",
  "direction": "Outbound",
  "source_amount": {
    "amount": 123.45,
    "currency": "USD"
  },
  "user_id": "5f7e3f7b-8e0c-48b8-9f2c-3e0c9d7a2a11",
  "card_id": "c0b2c7b1-1d2e-4d7a-9a5d-2b7e3c4d5f66",
  "created_at": "2024-01-01T10:00:00Z"
}
FieldDescription
typeCardTransaction
statusCompleted or Failed
directionOutbound (purchase) or Inbound (refund)
source_amountTransaction amount and currency

Handling Webhooks

app.post('/v2/webhooks/activities', async (req, res) => {
  const { type, status, direction, source_amount, user_id } = req.body;

  if (type !== 'CardTransaction' || status !== 'Completed') {
    return res.sendStatus(200);
  }

  if (direction === 'Outbound') {
    // Purchase: debit user balance
    await db.debitUserBalance(user_id, source_amount.amount, source_amount.currency);
  } else {
    // Refund: credit user balance
    await db.creditUserBalance(user_id, source_amount.amount, source_amount.currency);
  }

  res.sendStatus(200);
});
@app.post("/v2/webhooks/activities")
async def handle_activity_webhook(request: ActivityWebhook):
    if request.type != "CardTransaction" or request.status != "Completed":
        return Response(status_code=200)

    if request.direction == "Outbound":
        # Purchase: debit user balance
        await db.debit_user_balance(
            request.user_id,
            request.source_amount.amount,
            request.source_amount.currency
        )
    else:
        # Refund: credit user balance
        await db.credit_user_balance(
            request.user_id,
            request.source_amount.amount,
            request.source_amount.currency
        )

    return Response(status_code=200)
func handleActivityWebhook(w http.ResponseWriter, r *http.Request) {
    var activity ActivityWebhook
    json.NewDecoder(r.Body).Decode(&activity)

    if activity.Type != "CardTransaction" || activity.Status != "Completed" {
        w.WriteHeader(http.StatusOK)
        return
    }

    if activity.Direction == "Outbound" {
        // Purchase: debit user balance
        db.DebitUserBalance(activity.UserID, activity.SourceAmount.Amount, activity.SourceAmount.Currency)
    } else {
        // Refund: credit user balance
        db.CreditUserBalance(activity.UserID, activity.SourceAmount.Amount, activity.SourceAmount.Currency)
    }

    w.WriteHeader(http.StatusOK)
}

Balance Reconciliation

Your system is the source of truth for user balances. Wirex only tracks the Master Account balance.

Reconciliation approach:

  1. Track all authorization requests you approve
  2. Match against transaction webhooks received
  3. The Master Account balance should equal the sum of all processed transactions

Discrepancies may occur when:

  • Mandatory transactions processed without your approval
  • Network timeouts where transaction actually succeeded
  • Chargebacks or reversals

Always use unique_operation_id for idempotent handling.


FAQ

Q: What if I approve a transaction but the Master Account has insufficient funds? A: The transaction is declined. Monitor and maintain Master Account balance.

Q: How do refunds work? A: Refunds are mandatory Credit transactions. The Master Account is credited, and you receive a webhook to credit the user's balance.

Q: Can I query transaction history? A: Use the standard Activity History endpoints. Transactions are attributed to users by their EOA identifier.