> ## Documentation Index
> Fetch the complete documentation index at: https://partner-docs.contro.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# card.transaction

> Fired on card transaction lifecycle events

## Event: `card.transaction`

Sent when a card transaction status changes. The `status` field indicates the type of event. A single transaction may generate multiple `card.transaction` events as it moves through its lifecycle.

### Transaction lifecycle

```
Authorization request
  ├─ approved  → status: "authorized"
  └─ declined  → status: "declined"

Settlement (via card network or daily reconciliation)
  ├─ cleared   → status: "settled"
  └─ reversed  → status: "reversed"

Fee charged   → fee field present (only on authorized)
```

## Payload

The payload always includes a `status` field. Additional fields are included depending on the status.

### Common fields

| Field | Type | Description |
| - | - | - |
| `status` | string | One of `authorized`, `settled`, `declined`, `reversed` |
| `transactionId` | string | Unique transaction identifier |
| `cardId` | string | Card associated with the transaction |
| `amount` | number | Transaction amount in base units. Divide by 10^`currencyPrecision` — do not assume cents |
| `currency` | string | ISO 4217 currency code |
| `currencyPrecision` | number \| null | Decimal places `amount` is scaled by. 8 for provider transactions, independent of `currency` |
| `billingAmount` | number \| null | Billing amount in base units of the billing asset. Present for cross-currency transactions |
| `billingCurrencyCode` | string \| null | Billing currency code (e.g. `USDC`) |
| `billingCurrencyPrecision` | number \| null | Decimal places `billingAmount` is scaled by (USDC -> 6) |
| `merchant` | string \| null | Merchant name |
| `timestamp` | string | ISO 8601 event timestamp |

### Status: `authorized`

Sent when a card transaction authorization is approved and a hold is placed on funds. If a settlement fee applies, the `fee` field is included.

```json theme={null}
{
  "status": "authorized",
  "transactionId": "txn_abc123",
  "cardId": "card_xyz789",
  "amount": 500000000,
  "currency": "USD",
  "currencyPrecision": 8,
  "billingAmount": 6750000,
  "billingCurrencyCode": "USDC",
  "billingCurrencyPrecision": 6,
  "merchant": "Coffee Shop",
  "timestamp": "2026-04-16T10:00:00Z",
  "fee": 25
}
```

| Field | Type | Description |
| - | - | - |
| `fee` | number \| undefined | Settlement fee in fiat base units (10^8), if applicable |

### Status: `settled`

Sent when an authorized transaction is settled (cleared) by the card network. The settled amount may differ from the authorized amount.

```json theme={null}
{
  "status": "settled",
  "transactionId": "txn_abc123",
  "cardId": "card_xyz789",
  "amount": 500000000,
  "currency": "USD",
  "currencyPrecision": 8,
  "billingAmount": 6750000,
  "billingCurrencyCode": "USDC",
  "billingCurrencyPrecision": 6,
  "merchant": "Coffee Shop",
  "timestamp": "2026-04-16T10:00:00Z",
  "settledAmount": 4950000,
  "settledAmountPrecision": 6,
  "clearedAt": "2026-04-17T08:00:00Z"
}
```

| Field | Type | Description |
| - | - | - |
| `settledAmount` | number | Final settled amount in base units. Scale is given by `settledAmountPrecision` |
| `settledAmountPrecision` | number | Decimal places `settledAmount` is scaled by |
| `clearedAt` | string | ISO 8601 settlement timestamp |

### Status: `declined`

Sent when a card transaction authorization is declined.

```json theme={null}
{
  "status": "declined",
  "transactionId": "txn_abc123",
  "cardId": "card_xyz789",
  "amount": 500000000,
  "currency": "USD",
  "merchant": "Coffee Shop",
  "timestamp": "2026-04-16T10:00:00Z",
  "reason": "Insufficient balance"
}
```

| Field | Type | Description |
| - | - | - |
| `reason` | string | Decline reason |

### Status: `reversed`

Sent when a transaction is fully or partially reversed/refunded. Use `originalAmount` and `amount` to determine if this is a partial or full reversal.

#### Full reversal

The entire authorized amount is released. The transaction is voided.

```json theme={null}
{
  "status": "reversed",
  "transactionId": "txn_abc123",
  "cardId": "card_xyz789",
  "amount": 500000000,
  "currency": "USD",
  "merchant": "Coffee Shop",
  "timestamp": "2026-04-16T10:00:00Z",
  "reversalType": "reversal",
  "originalAmount": 5000
}
```

#### Partial reversal

Part of the authorized amount is released. The remaining hold continues to settlement.

```json theme={null}
{
  "status": "reversed",
  "transactionId": "txn_abc123",
  "cardId": "card_xyz789",
  "amount": 2000,
  "currency": "USD",
  "merchant": "Coffee Shop",
  "timestamp": "2026-04-16T10:00:00Z",
  "reversalType": "partial_reversal",
  "originalAmount": 5000
}
```

| Field | Type | Description |
| - | - | - |
| `reversalType` | string | `"reversal"`, `"partial_reversal"`, `"refund"`, or `"partial_refund"` |
| `originalAmount` | number | Original authorized or settled amount in base units (see `currencyPrecision`) |
| `amount` | number | The reversed/refunded amount in base units (see `currencyPrecision`) |

**Determining full vs partial:** Compare `amount` to `originalAmount`. If they are equal, it is a full reversal. If `amount < originalAmount`, it is partial.

## Response

Your endpoint must return a **2xx** status code within **30 seconds** to acknowledge receipt. Any non-2xx response or timeout triggers the [retry policy](/partner/webhooks#retry-policy).

| Status code | Meaning |
| - | - |
| `200` | Event received and processed |
| `202` | Event received, will process asynchronously |
| Any non-2xx | Delivery failed — will retry |

## Example handler

```javascript theme={null}
app.post("/webhooks/contro", (req, res) => {
  const eventType = req.headers["x-contro-event"];

  if (eventType === "card.transaction") {
    const { status, transactionId, cardId, amount, currency } = req.body;

    switch (status) {
      case "authorized":
        // Record authorization hold; check req.body.fee for settlement fee
        break;
      case "settled":
        // Finalize transaction; req.body.settledAmount may differ from amount
        break;
      case "declined":
        // Log declined transaction; req.body.reason has the decline reason
        break;
      case "reversed": {
        // req.body.reversalType: "reversal", "partial_reversal", "refund", "partial_refund"
        const { amount, originalAmount, reversalType } = req.body;
        const isPartial = amount < originalAmount;
        // Handle full or partial reversal/refund
        break;
      }
    }
  }

  res.status(200).send("OK");
});
```
