Authorization API
Implement the authorization endpoints Wirex calls before processing transactions.
Before You Start
Read the following guides before proceeding:
| Guide | Why |
|---|---|
| Overview | Understand 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
| Metric | Requirement |
|---|---|
| Target response time | < 300ms |
| Maximum round trip | 500ms |
| Timeout behavior | Declined (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"
}| Field | Type | Required | Description |
|---|---|---|---|
transaction_type | string | Yes | Channel type: POS, ATM, ePOS, MoneyTransfer |
user_id | UUID | Yes | User identifier |
transaction_id | UUID | Yes | Card transaction identifier |
operation_type | string | Yes | Debit (purchase/withdrawal) or Credit (refund) |
card_id | UUID | Yes | Card identifier |
currency | string | Yes | ISO 4217 currency code |
amount | decimal | Yes | Transaction amount |
unique_operation_id | UUID | Yes | Unique operation identifier |
correlation_id | UUID | Yes | Correlation identifier for tracing |
rates_by_assets | array | Yes | Exchange rates by asset |
is_mandatory | boolean | Yes | If true, processed regardless of response |
related_external_operation_status | string | Yes | Status of related operation (see below) |
transaction_reason | string | Yes | Transaction reason (see below) |
related_external_operation_status Values
| Value | Description |
|---|---|
None | First transaction in the chain |
Rejected | Previous transaction was rejected by partner |
Approved | Previous transaction was approved by partner |
transaction_reason Values
| Value | Description |
|---|---|
Authorization | Authorization operation (single or incremental) |
Clearing | Capture operation (direct, single, or multiple) |
Refund | Refund operation (partial or full) |
Reversal | Cancel authorizations/clearings (partial or full) |
OriginalCredit | Credit operation |
Representment | Cancel refunds/chargebacks |
MoneySend | Credit operation |
DelayAuthorization | Fuel 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"
}
]
}| Field | Type | Required | Description |
|---|---|---|---|
transaction_id | UUID | Yes | Card transaction ID (echo back) |
is_allowed | boolean | Yes | true to approve, false to decline |
decline_reason | string | No | InsufficientFunds or GeneralError |
external_transactions | array | Yes | External transactions performed |
external_transactions Fields
| Field | Type | Required | Description |
|---|---|---|---|
account_currency | string | Yes | Asset name (e.g., USDC, BTC) |
account_id | string | Yes | Account ID related to card |
amount | decimal | Yes | Transaction amount |
rate | decimal | Yes | Exchange rate used |
transaction_identifier | string | Yes | Your 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
}| Field | Type | Required | Description |
|---|---|---|---|
transaction_type | string | Yes | Channel type |
user_id | UUID | Yes | User identifier |
transaction_id | UUID | Yes | Card transaction identifier |
operation_type | string | Yes | Debit or Credit |
card_id | UUID | Yes | Card identifier |
currency | string | Yes | Original transaction currency |
amount | decimal | Yes | Original transaction amount |
unique_operation_id | UUID | Yes | Unique operation identifier |
correlation_id | UUID | Yes | Correlation identifier |
settlement_currency | string | Yes | Settlement currency |
settlement_amount | decimal | Yes | Settlement amount |
settlement_decision | enum | Yes | Approve or Reject |
reject_reason | string | No | Reason for rejection (if rejecting) |
Response
{
"is_processed": true,
"error_reason": null,
"transaction_id": "a7b2a4b3-2f61-41a7-9f5a-3f3f0b1a3d22"
}| Field | Type | Required | Description |
|---|---|---|---|
is_processed | boolean | Yes | Whether settlement was processed |
error_reason | string | No | InsufficientFunds or GeneralError |
transaction_id | UUID | Yes | Related transaction ID |
Mandatory vs Non-Mandatory Transactions
| Field Value | Authorization Behavior | Settlement Behavior |
|---|---|---|
is_mandatory: false | Requires is_allowed: true | Normal processing |
is_mandatory: true | Processed regardless of response | Processed regardless of response |
Mandatory transactions include refunds, chargebacks, and network corrections. You are notified but cannot decline them.
Authorization Behavior Summary
| Scenario | Non-Mandatory | Mandatory |
|---|---|---|
is_allowed: true | Processed | Processed |
is_allowed: false | Declined | Processed |
| Timeout (>500ms) | Declined | Processed |
| 5xx error | Declined | Processed |
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)
Updated 24 days ago
