> ## Documentation Index
> Fetch the complete documentation index at: https://apidocs.bridge.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Real-time Authorization

> Make real-time authorization decisions for card transactions

<Warning>
  This page is part of the legacy Bridge Cards API. For new integrations, use the [consumer issuing guide](/platform/cards/overview/stripe-issuing) instead.
</Warning>

<Warning>
  **Consider carefully before enabling.** Due to the constraints of on-chain transactions, card authorizations operate within a tight latency window. Adding real-time authorization introduces additional latency that may result in increased timeout rates and declined transactions. We recommend enabling this feature only if your use case requires real-time decisioning that cannot be achieved through Bridge's built-in authorization controls.
</Warning>

## **Overview**

Real-time authorization allows you to make custom approval or decline decisions during card transactions. When a cardholder attempts to make a purchase, Bridge calls your webhook endpoint synchronously, enabling you to apply your own business logic before the transaction is approved or declined.

This gives you fine-grained control over card spending, such as:

* Custom spending limits and velocity controls
* Merchant category restrictions
* Geographic restrictions
* Fraud detection and risk scoring

<Note>
  Real-time authorization is optional. If not configured, Bridge will authorize transactions based on available balance and other built-in controls.
</Note>

## **Quick Start**

1. Set up an HTTPS endpoint that can handle POST requests
2. Configure your webhook URL via the Bridge API
3. Implement signature verification for security
4. Return authorization decisions within the timeout window

## **How It Works**

When a card is used for a purchase, the following flow occurs:

1. **Card network receives transaction**: The cardholder initiates a purchase
2. **Bridge receives authorization request**: The card network sends the authorization to Bridge
3. **Bridge calls your webhook**: Bridge forwards the authorization details to your configured endpoint
4. **Your decision**: Your webhook returns an approve or decline decision
5. **Bridge responds to network**: Bridge relays your decision back to the card network
6. **Transaction completes**: The purchase is approved or declined

```
Cardholder → Card Network → Bridge → Your Webhook → Bridge → Card Network → Merchant
```

<Warning>
  Even if your webhook returns `approved: true`, the transaction may still be declined if the total end-to-end latency exceeds the card network's timeout. You will be informed of the final authorization result through the regular [card transaction webhooks](/platform/cards/overview/webhooks).
</Warning>

## **Webhook Request**

When a card transaction requires authorization, Bridge sends a POST request to your configured webhook URL.

### Request Headers

| Header                | Description                            |
| :-------------------- | :------------------------------------- |
| `X-Webhook-Signature` | RSA signature for payload verification |
| `Content-Type`        | `application/json`                     |

### Request Body Example

```json expandable theme={null}
{
  "event_id": "dc85e7d3-dce7-49ca-8023-2a74c65eca29",
  "api_version": "v0",
  "timestamp": "2025-06-09T15:30:00.000Z",
  "data": {
    "authorization_id": "06e774a7-8a54-48f8-b5b7-4c266403f560",
    "auth_type": "auth",
    "partial_supported": false,
    "network": "visa",
    "international": false,
    "original_authorization_id": null,
    "transaction_id": "00b4b744-375d-499f-824f-db1dcca995dd",
    "account": {
      "last_4": "5198"
    },
    "currency": "usd",
    "amount": "-25.50",
    "billing_amount": "-25.50",
    "cashback_amount": "0.00",
    "local_transaction_details": {
      "amount": "-25.50",
      "currency": "usd",
      "exchange_rate": "1.0000"
    },
    "merchant": {
      "description": "COFFEE SHOP",
      "postal_code": "94025",
      "state": "CA",
      "country": "USA",
      "category": "fast_food_restaurants",
      "category_code": "5814"
    },
    "entry_method": "chip",
    "card_present": true,
    "recurring": false,
    "verification_data": {
      "cvv_check": "match",
      "address_check": "match",
      "address_postal_code_check": "match",
      "pin_check": "verified",
      "three_d_secure_check": "authenticated"
    },
    "wallet": null,
    "card_account_id": "5bfb3f83-ebf2-482d-a215-4c3c5bf99c64",
    "customer_id": "4690efc8-4d35-4bfc-8f89-425f0a4a115b",
    "created_at": "2025-06-09T15:30:00.000Z"
  }
}
```

### Field Reference

#### Top-level Fields

| Field         | Type              | Description                                      |
| :------------ | :---------------- | :----------------------------------------------- |
| `event_id`    | string (UUID)     | Unique identifier for this authorization request |
| `api_version` | string            | API version of the payload schema                |
| `timestamp`   | string (ISO-8601) | UTC timestamp when the event was generated       |
| `data`        | object            | Authorization data (see below)                   |

#### Data Object

| Field                       | Type              | Description                                               |
| :-------------------------- | :---------------- | :-------------------------------------------------------- |
| `authorization_id`          | string (UUID)     | Unique identifier for this authorization                  |
| `auth_type`                 | string            | Type of authorization: `auth` or `incremental_auth`       |
| `partial_supported`         | boolean           | Whether the merchant supports partial authorization       |
| `network`                   | string            | Card network: `visa`, `mastercard`, `discover`, `amex`    |
| `international`             | boolean           | Whether this is a cross-border transaction                |
| `original_authorization_id` | string/null       | Reference to original auth for incremental authorizations |
| `transaction_id`            | string (UUID)     | Card transaction identifier                               |
| `account.last_4`            | string            | Last 4 digits of the card PAN                             |
| `currency`                  | string            | Transaction currency (ISO-4217)                           |
| `amount`                    | string            | Transaction amount (negative for purchases)               |
| `billing_amount`            | string            | Amount in billing currency                                |
| `cashback_amount`           | string            | Requested cashback amount                                 |
| `card_account_id`           | string (UUID)     | Bridge card account identifier                            |
| `customer_id`               | string (UUID)     | Bridge customer identifier                                |
| `created_at`                | string (ISO-8601) | When the authorization was initiated                      |

#### Merchant Object

| Field           | Type        | Description                  |
| :-------------- | :---------- | :--------------------------- |
| `description`   | string/null | Merchant name/descriptor     |
| `postal_code`   | string/null | Merchant postal/ZIP code     |
| `state`         | string/null | Merchant state/province code |
| `country`       | string/null | Merchant country code        |
| `category`      | string/null | MCC category name            |
| `category_code` | string      | Merchant Category Code (MCC) |

#### Local Transaction Details

| Field           | Type   | Description                                    |
| :-------------- | :----- | :--------------------------------------------- |
| `amount`        | string | Amount in merchant's local currency            |
| `currency`      | string | Merchant's local currency code                 |
| `exchange_rate` | string | Conversion rate from local to billing currency |

#### Verification Data

| Field                       | Type        | Values                                                                |
| :-------------------------- | :---------- | :-------------------------------------------------------------------- |
| `cvv_check`                 | string      | `match`, `mismatch`, `not_provided`                                   |
| `address_check`             | string      | `match`, `mismatch`, `not_provided`                                   |
| `address_postal_code_check` | string      | `match`, `mismatch`, `not_provided`                                   |
| `pin_check`                 | string/null | `verified`, `failed`, `blocked`, `locked`, `not_set`, `no_pin_passed` |
| `three_d_secure_check`      | string/null | `attempt_acknowledged`, `authenticated`, `failed`, `required`         |

#### Entry Method Values

| Value         | Description                 |
| :------------ | :-------------------------- |
| `chip`        | EMV chip transaction        |
| `contactless` | NFC/contactless transaction |
| `swipe`       | Magnetic stripe transaction |
| `manual`      | Manually keyed transaction  |
| `online`      | E-commerce transaction      |
| `other`       | Other entry method          |

#### Wallet Values

| Value         | Description               |
| :------------ | :------------------------ |
| `apple_pay`   | Apple Pay                 |
| `google_pay`  | Google Pay                |
| `samsung_pay` | Samsung Pay               |
| `other`       | Other digital wallet      |
| `null`        | Physical card (no wallet) |

## **Webhook Response**

Your webhook must return a JSON response with HTTP status code `200`.

### Response Headers

| Header         | Value              |
| :------------- | :----------------- |
| `Content-Type` | `application/json` |

### Response Body

| Field             | Required | Type    | Description                            |
| :---------------- | :------- | :------ | :------------------------------------- |
| `approved`        | Yes      | boolean | `true` to approve, `false` to decline  |
| `balance`         | No       | object  | Current user balance on your platform  |
| `decision_reason` | No       | string  | Human-readable reason for the decision |

### Response Examples

<CodeGroup>
  ```json Simple Approval theme={null}
  {
    "approved": true
  }
  ```

  ```json Approval with Balance theme={null}
  {
    "approved": true,
    "balance": {
      "amount": "125.00",
      "currency": "usdc"
    }
  }
  ```

  ```json Decline - Insufficient Funds theme={null}
  {
    "approved": false,
    "decision_reason": "Insufficient funds"
  }
  ```

  ```json Decline - Fraud Risk theme={null}
  {
    "approved": false,
    "decision_reason": "Transaction flagged by fraud detection"
  }
  ```
</CodeGroup>

## **Security**

### HTTPS Requirement

Your webhook endpoint **must** use HTTPS. HTTP endpoints are not supported.

### Signature Verification

Every webhook request includes an RSA signature in the `X-Webhook-Signature` header using the format:

```
t=<timestamp>,v0=<base64-encoded-signature>
```

You should verify this signature to ensure request authenticity and freshness.

#### Verification Steps

1. Extract the timestamp (`t`) and signature (`v0`) from the header
2. Check that the timestamp is recent (reject events older than 10 minutes)
3. Create the signed payload string: `{timestamp}.{raw_request_body}`
4. Verify the RSA signature using Bridge's public key

<Info>
  Bridge's public key is provided when you configure your webhook endpoint. You can retrieve it via the webhook settings API.
</Info>

## **Timeouts and Fallback Behavior**

### Timeout Configuration

The default timeout for webhook responses is **500ms**. If your webhook doesn't respond within this window:

1. Bridge stops waiting for your response
2. The configured fallback behavior is applied
3. The transaction continues based on fallback settings

### Fallback Modes

| Mode      | Description                                     |
| :-------- | :---------------------------------------------- |
| `DECLINE` | Automatically decline the transaction (default) |
| `APPROVE` | Automatically approve the transaction           |

<Warning>
  The default fallback mode is `DECLINE`. This means if your webhook is slow or unavailable, transactions will be declined. Consider your use case carefully before changing to `APPROVE`.
</Warning>

## **Configure Webhook Settings**

### Retrieve Current Settings

```bash theme={null}
curl --location 'https://api.bridge.xyz/v0/developer/cards/auth_webhook_settings' \
  --header 'Api-Key: <your-api-key>'
```

**Response:**

```json theme={null}
{
  "enabled": true,
  "webhook_url": "https://your-domain.com/webhooks/card-auth",
  "timeout_ms": 500,
  "fallback_mode": "DECLINE",
  "public_key": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"
}
```

### Create Webhook Configuration

```bash theme={null}
curl --location --request POST 'https://api.bridge.xyz/v0/developer/cards/auth_webhook_settings' \
  --header 'Api-Key: <your-api-key>' \
  --header 'Idempotency-Key: <idempotency-key>' \
  --header 'Content-Type: application/json' \
  --data '{
    "enabled": true,
    "webhook_url": "https://your-domain.com/webhooks/card-auth"
  }'
```

### Update Webhook Configuration

**Update webhook URL:**

```bash theme={null}
curl --location --request PUT 'https://api.bridge.xyz/v0/developer/cards/auth_webhook_settings' \
  --header 'Api-Key: <your-api-key>' \
  --header 'Content-Type: application/json' \
  --data '{
    "enabled": true,
    "webhook_url": "https://your-domain.com/webhooks/card-auth-v2"
  }'
```

**Disable webhook:**

```bash theme={null}
curl --location --request PUT 'https://api.bridge.xyz/v0/developer/cards/auth_webhook_settings' \
  --header 'Api-Key: <your-api-key>' \
  --header 'Content-Type: application/json' \
  --data '{
    "enabled": false
  }'
```

## **Webhook Handler Integration**

Here's a complete example webhook handler in Python:

```python expandable theme={null}
from flask import Flask, request, jsonify
import json
import base64
import time
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding

app = Flask(__name__)

# Replace with your actual public key from webhook settings
WEBHOOK_PUBLIC_KEY = """-----BEGIN PUBLIC KEY-----
your_webhook_public_key_here
-----END PUBLIC KEY-----"""

def verify_webhook_signature(payload: bytes, signature_header: str, public_key_pem: str) -> bool:
    try:
        # Parse signature header
        signature_parts = signature_header.split(',')
        timestamp = next((part.split('=', 1)[1] for part in signature_parts if part.startswith('t=')), None)
        signature = next((part.split('=', 1)[1] for part in signature_parts if part.startswith('v0=')), None)
        
        if not timestamp or not signature:
            return False
        
        # Check timestamp (reject events older than 10 minutes)
        current_time = int(time.time() * 1000)
        if current_time - int(timestamp) > 600000:
            return False
        
        # Create signed payload
        signed_payload = f"{timestamp}.{payload.decode()}"
        
        # Verify signature
        public_key = serialization.load_pem_public_key(public_key_pem.encode())
        signature_bytes = base64.b64decode(signature)
        
        public_key.verify(
            signature_bytes,
            signed_payload.encode(),
            padding.PKCS1v15(),
            hashes.SHA256()
        )
        return True
    except Exception:
        return False

@app.route('/webhooks/card-auth', methods=['POST'])
def handle_card_authorization():
    try:
        payload = request.get_data()
        signature_header = request.headers.get('X-Webhook-Signature')
        
        if not signature_header:
            return jsonify({'error': 'Missing signature header'}), 400
        
        # Verify signature
        if not verify_webhook_signature(payload, signature_header, WEBHOOK_PUBLIC_KEY):
            return jsonify({'error': 'Invalid signature'}), 400
        
        # Parse webhook data
        webhook_data = json.loads(payload)
        auth_data = webhook_data['data']
        
        # Extract authorization details
        amount = abs(float(auth_data['amount']))
        customer_id = auth_data['customer_id']
        merchant = auth_data['merchant']
        
        # Implement your business logic here
        # Examples:
        # - Check user balance
        # - Validate merchant category
        # - Check spending limits
        # - Run fraud detection
        
        # Approve the transaction
        return jsonify({
            'approved': True
        })
        
    except Exception as e:
        print(f"Webhook error: {e}")
        return jsonify({'error': 'Internal server error'}), 500

if __name__ == '__main__':
    app.run(port=3000)
```

<Tip>
  Test your webhook integration thoroughly in the sandbox environment before going live. Simulate various scenarios including timeouts, errors, and edge cases.
</Tip>
