Authorization API

Implement the authorization endpoints Wirex calls before processing transactions.

Before You Start

Read the following guides before proceeding:

GuideWhy
OverviewUnderstand External Authorization model

How It Works

When a user initiates a card transaction, Wirex calls your authorization endpoint. After authorization, a separate settlement call finalizes the transaction.

sequenceDiagram
    participant User
    participant Network as Card Network
    participant Wirex as Wirex
    participant Partner as Your API
    participant Master as Master Account

    User->>Network: Card transaction
    Network->>Wirex: Authorization request
    Wirex->>Partner: POST .../external/authorize
    Partner-->>Wirex: { is_allowed: true }
    Note over Wirex: Hold on Master Account
    Wirex-->>Network: Authorized

    Network->>Wirex: Settlement request
    Wirex->>Partner: POST .../external/settle
    Partner-->>Wirex: { is_processed: true }
    Wirex->>Master: Debit Master Account
    Wirex-->>Network: Settled

Performance Requirements

MetricRequirement
Target response time< 300ms
Maximum round trip500ms
Timeout behaviorDeclined (for non-mandatory transactions)

[!IMPORTANT] Deploy your API in the same region as Wirex (AWS eu-central-1 / Azure west-europe) for optimal latency.


Authorization Endpoint

Authorize a card transaction before processing.

POST /external-authorization/on-chain/card-transaction/external/authorize

Request

{
  "transaction_type": "POS",
  "user_id": "5f7e3f7b-8e0c-48b8-9f2c-3e0c9d7a2a11",
  "transaction_id": "a7b2a4b3-2f61-41a7-9f5a-3f3f0b1a3d22",
  "operation_type": "Debit",
  "card_id": "c0b2c7b1-1d2e-4d7a-9a5d-2b7e3c4d5f66",
  "currency": "USD",
  "amount": 123.45,
  "unique_operation_id": "3e1f6a98-6b0a-4c8c-93e5-0b9c6d2a7f31",
  "correlation_id": "7a8b9c0d-1e2f-3a4b-5c6d-7e8f9a0b1c2d",
  "rates_by_assets": [
    {
      "asset": "USDC",
      "rate": 1.0
    }
  ],
  "is_mandatory": false,
  "related_external_operation_status": "None",
  "transaction_reason": "Authorization"
}
FieldTypeRequiredDescription
transaction_typestringYesChannel type: POS, ATM, ePOS, MoneyTransfer
user_idUUIDYesUser identifier
transaction_idUUIDYesCard transaction identifier
operation_typestringYesDebit (purchase/withdrawal) or Credit (refund)
card_idUUIDYesCard identifier
currencystringYesISO 4217 currency code
amountdecimalYesTransaction amount
unique_operation_idUUIDYesUnique operation identifier
correlation_idUUIDYesCorrelation identifier for tracing
rates_by_assetsarrayYesExchange rates by asset
is_mandatorybooleanYesIf true, processed regardless of response
related_external_operation_statusstringYesStatus of related operation (see below)
transaction_reasonstringYesTransaction reason (see below)

related_external_operation_status Values

ValueDescription
NoneFirst transaction in the chain
RejectedPrevious transaction was rejected by partner
ApprovedPrevious transaction was approved by partner

transaction_reason Values

ValueDescription
AuthorizationAuthorization operation (single or incremental)
ClearingCapture operation (direct, single, or multiple)
RefundRefund operation (partial or full)
ReversalCancel authorizations/clearings (partial or full)
OriginalCreditCredit operation
RepresentmentCancel refunds/chargebacks
MoneySendCredit operation
DelayAuthorizationFuel dispenser scenarios or chargebacks

Response

{
  "transaction_id": "a7b2a4b3-2f61-41a7-9f5a-3f3f0b1a3d22",
  "is_allowed": true,
  "decline_reason": null,
  "external_transactions": [
    {
      "account_currency": "USDC",
      "account_id": "user-account-123",
      "amount": 123.45,
      "rate": 1.0,
      "transaction_identifier": "partner-txn-456"
    }
  ]
}
FieldTypeRequiredDescription
transaction_idUUIDYesCard transaction ID (echo back)
is_allowedbooleanYestrue to approve, false to decline
decline_reasonstringNoInsufficientFunds or GeneralError
external_transactionsarrayYesExternal transactions performed

external_transactions Fields

FieldTypeRequiredDescription
account_currencystringYesAsset name (e.g., USDC, BTC)
account_idstringYesAccount ID related to card
amountdecimalYesTransaction amount
ratedecimalYesExchange rate used
transaction_identifierstringYesYour transaction reference

Settlement Endpoint

Approve or reject a previously authorized transaction.

POST /external-authorization/on-chain/card-transaction/external/settle

Request

{
  "transaction_type": "POS",
  "user_id": "5f7e3f7b-8e0c-48b8-9f2c-3e0c9d7a2a11",
  "transaction_id": "a7b2a4b3-2f61-41a7-9f5a-3f3f0b1a3d22",
  "operation_type": "Debit",
  "card_id": "c0b2c7b1-1d2e-4d7a-9a5d-2b7e3c4d5f66",
  "currency": "USD",
  "amount": 123.45,
  "unique_operation_id": "3e1f6a98-6b0a-4c8c-93e5-0b9c6d2a7f31",
  "correlation_id": "7a8b9c0d-1e2f-3a4b-5c6d-7e8f9a0b1c2d",
  "settlement_currency": "USD",
  "settlement_amount": 123.45,
  "settlement_decision": "Approve",
  "reject_reason": null
}
FieldTypeRequiredDescription
transaction_typestringYesChannel type
user_idUUIDYesUser identifier
transaction_idUUIDYesCard transaction identifier
operation_typestringYesDebit or Credit
card_idUUIDYesCard identifier
currencystringYesOriginal transaction currency
amountdecimalYesOriginal transaction amount
unique_operation_idUUIDYesUnique operation identifier
correlation_idUUIDYesCorrelation identifier
settlement_currencystringYesSettlement currency
settlement_amountdecimalYesSettlement amount
settlement_decisionenumYesApprove or Reject
reject_reasonstringNoReason for rejection (if rejecting)

Response

{
  "is_processed": true,
  "error_reason": null,
  "transaction_id": "a7b2a4b3-2f61-41a7-9f5a-3f3f0b1a3d22"
}
FieldTypeRequiredDescription
is_processedbooleanYesWhether settlement was processed
error_reasonstringNoInsufficientFunds or GeneralError
transaction_idUUIDYesRelated transaction ID

Mandatory vs Non-Mandatory Transactions

Field ValueAuthorization BehaviorSettlement Behavior
is_mandatory: falseRequires is_allowed: trueNormal processing
is_mandatory: trueProcessed regardless of responseProcessed regardless of response

Mandatory transactions include refunds, chargebacks, and network corrections. You are notified but cannot decline them.


Authorization Behavior Summary

ScenarioNon-MandatoryMandatory
is_allowed: trueProcessedProcessed
is_allowed: falseDeclinedProcessed
Timeout (>500ms)DeclinedProcessed
5xx errorDeclinedProcessed

Implementation Example

// Authorization endpoint
app.post('/external-authorization/on-chain/card-transaction/external/authorize', async (req, res) => {
  const {
    user_id,
    transaction_id,
    amount,
    currency,
    is_mandatory,
    unique_operation_id,
    transaction_reason
  } = req.body;

  // Check idempotency
  const existing = await db.findOperation(unique_operation_id);
  if (existing) {
    return res.json({
      transaction_id,
      is_allowed: existing.allowed,
      external_transactions: existing.external_transactions
    });
  }

  // For mandatory transactions, always allow
  if (is_mandatory) {
    const result = await db.recordOperation(unique_operation_id, user_id, amount, true);
    return res.json({
      transaction_id,
      is_allowed: true,
      external_transactions: [{
        account_currency: currency,
        account_id: user_id,
        amount,
        rate: 1.0,
        transaction_identifier: result.id
      }]
    });
  }

  // Check user balance
  const userBalance = await db.getUserBalance(user_id, currency);
  const allowed = userBalance >= amount;

  if (allowed) {
    await db.holdBalance(user_id, amount, unique_operation_id);
  }

  const result = await db.recordOperation(unique_operation_id, user_id, amount, allowed);

  res.json({
    transaction_id,
    is_allowed: allowed,
    decline_reason: allowed ? null : 'InsufficientFunds',
    external_transactions: allowed ? [{
      account_currency: currency,
      account_id: user_id,
      amount,
      rate: 1.0,
      transaction_identifier: result.id
    }] : []
  });
});

// Settlement endpoint
app.post('/external-authorization/on-chain/card-transaction/external/settle', async (req, res) => {
  const {
    transaction_id,
    user_id,
    unique_operation_id,
    settlement_amount,
    settlement_currency,
    settlement_decision
  } = req.body;

  const operation = await db.findOperation(unique_operation_id);
  if (!operation) {
    return res.json({
      is_processed: false,
      error_reason: 'GeneralError',
      transaction_id
    });
  }

  if (settlement_decision === 'Approve') {
    await db.finalizeDebit(user_id, settlement_amount, settlement_currency, unique_operation_id);
  } else {
    await db.releaseHold(user_id, unique_operation_id);
  }

  res.json({
    is_processed: true,
    transaction_id
  });
});
# Authorization endpoint
@app.post("/external-authorization/on-chain/card-transaction/external/authorize")
async def authorize_transaction(request: AuthorizeRequest):
    # Check idempotency
    existing = await db.find_operation(request.unique_operation_id)
    if existing:
        return {
            "transaction_id": request.transaction_id,
            "is_allowed": existing.allowed,
            "external_transactions": existing.external_transactions
        }

    # For mandatory transactions, always allow
    if request.is_mandatory:
        result = await db.record_operation(request.unique_operation_id, request.user_id, request.amount, True)
        return {
            "transaction_id": request.transaction_id,
            "is_allowed": True,
            "external_transactions": [{
                "account_currency": request.currency,
                "account_id": request.user_id,
                "amount": request.amount,
                "rate": 1.0,
                "transaction_identifier": result.id
            }]
        }

    # Check user balance
    user_balance = await db.get_user_balance(request.user_id, request.currency)
    allowed = user_balance >= request.amount

    if allowed:
        await db.hold_balance(request.user_id, request.amount, request.unique_operation_id)

    result = await db.record_operation(request.unique_operation_id, request.user_id, request.amount, allowed)

    return {
        "transaction_id": request.transaction_id,
        "is_allowed": allowed,
        "decline_reason": None if allowed else "InsufficientFunds",
        "external_transactions": [{
            "account_currency": request.currency,
            "account_id": request.user_id,
            "amount": request.amount,
            "rate": 1.0,
            "transaction_identifier": result.id
        }] if allowed else []
    }

# Settlement endpoint
@app.post("/external-authorization/on-chain/card-transaction/external/settle")
async def settle_transaction(request: SettleRequest):
    operation = await db.find_operation(request.unique_operation_id)
    if not operation:
        return {
            "is_processed": False,
            "error_reason": "GeneralError",
            "transaction_id": request.transaction_id
        }

    if request.settlement_decision == "Approve":
        await db.finalize_debit(request.user_id, request.settlement_amount, request.settlement_currency, request.unique_operation_id)
    else:
        await db.release_hold(request.user_id, request.unique_operation_id)

    return {
        "is_processed": True,
        "transaction_id": request.transaction_id
    }
// Authorization endpoint
func authorizeTransaction(w http.ResponseWriter, r *http.Request) {
    var req AuthorizeRequest
    json.NewDecoder(r.Body).Decode(&req)

    // Check idempotency
    if existing, found := db.FindOperation(req.UniqueOperationID); found {
        json.NewEncoder(w).Encode(map[string]interface{}{
            "transaction_id":        req.TransactionID,
            "is_allowed":            existing.Allowed,
            "external_transactions": existing.ExternalTransactions,
        })
        return
    }

    // For mandatory transactions, always allow
    if req.IsMandatory {
        result := db.RecordOperation(req.UniqueOperationID, req.UserID, req.Amount, true)
        json.NewEncoder(w).Encode(map[string]interface{}{
            "transaction_id": req.TransactionID,
            "is_allowed":     true,
            "external_transactions": []map[string]interface{}{{
                "account_currency":       req.Currency,
                "account_id":             req.UserID,
                "amount":                 req.Amount,
                "rate":                   1.0,
                "transaction_identifier": result.ID,
            }},
        })
        return
    }

    // Check user balance
    userBalance := db.GetUserBalance(req.UserID, req.Currency)
    allowed := userBalance >= req.Amount

    if allowed {
        db.HoldBalance(req.UserID, req.Amount, req.UniqueOperationID)
    }

    result := db.RecordOperation(req.UniqueOperationID, req.UserID, req.Amount, allowed)

    resp := map[string]interface{}{
        "transaction_id": req.TransactionID,
        "is_allowed":     allowed,
    }
    if !allowed {
        resp["decline_reason"] = "InsufficientFunds"
        resp["external_transactions"] = []map[string]interface{}{}
    } else {
        resp["external_transactions"] = []map[string]interface{}{{
            "account_currency":       req.Currency,
            "account_id":             req.UserID,
            "amount":                 req.Amount,
            "rate":                   1.0,
            "transaction_identifier": result.ID,
        }}
    }
    json.NewEncoder(w).Encode(resp)
}

// Settlement endpoint
func settleTransaction(w http.ResponseWriter, r *http.Request) {
    var req SettleRequest
    json.NewDecoder(r.Body).Decode(&req)

    operation, found := db.FindOperation(req.UniqueOperationID)
    if !found {
        json.NewEncoder(w).Encode(map[string]interface{}{
            "is_processed":   false,
            "error_reason":   "GeneralError",
            "transaction_id": req.TransactionID,
        })
        return
    }

    if req.SettlementDecision == "Approve" {
        db.FinalizeDebit(req.UserID, req.SettlementAmount, req.SettlementCurrency, req.UniqueOperationID)
    } else {
        db.ReleaseHold(req.UserID, req.UniqueOperationID)
    }

    json.NewEncoder(w).Encode(map[string]interface{}{
        "is_processed":   true,
        "transaction_id": req.TransactionID,
    })
}

Error Handling

Your API Unavailable

  • Non-mandatory transactions: Declined
  • Mandatory transactions: Processed anyway

Insufficient Master Account Balance

  • Non-mandatory transactions: Declined even if you approve
  • Mandatory transactions: Processed (Master Account may go negative)