Skip to main content

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

FieldTypeDescription
statusstringOne of authorized, settled, declined, reversed
transactionIdstringUnique transaction identifier
cardIdstringCard associated with the transaction
amountnumberTransaction amount in minor units (cents)
currencystringISO 4217 currency code
currencyPrecisionnumber | nullDecimal precision of the transaction currency
billingAmountnumber | nullBilling amount in the cardholder’s currency (minor units). Present for cross-currency transactions
billingCurrencyCodestring | nullISO 4217 billing currency code
billingCurrencyPrecisionnumber | nullDecimal precision of the billing currency
merchantstring | nullMerchant name
timestampstringISO 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.
{
  "status": "authorized",
  "transactionId": "txn_abc123",
  "cardId": "card_xyz789",
  "amount": 5000,
  "currency": "USD",
  "currencyPrecision": 2,
  "billingAmount": 6750,
  "billingCurrencyCode": "SGD",
  "billingCurrencyPrecision": 2,
  "merchant": "Coffee Shop",
  "timestamp": "2026-04-16T10:00:00Z",
  "fee": 25
}
FieldTypeDescription
feenumber | undefinedSettlement fee in minor units (cents), 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.
{
  "status": "settled",
  "transactionId": "txn_abc123",
  "cardId": "card_xyz789",
  "amount": 5000,
  "currency": "USD",
  "currencyPrecision": 2,
  "billingAmount": 6750,
  "billingCurrencyCode": "SGD",
  "billingCurrencyPrecision": 2,
  "merchant": "Coffee Shop",
  "timestamp": "2026-04-16T10:00:00Z",
  "settledAmount": 4950,
  "clearedAt": "2026-04-17T08:00:00Z"
}
FieldTypeDescription
settledAmountnumberFinal settled amount in minor units (cents)
clearedAtstringISO 8601 settlement timestamp

Status: declined

Sent when a card transaction authorization is declined.
{
  "status": "declined",
  "transactionId": "txn_abc123",
  "cardId": "card_xyz789",
  "amount": 5000,
  "currency": "USD",
  "merchant": "Coffee Shop",
  "timestamp": "2026-04-16T10:00:00Z",
  "reason": "Insufficient balance"
}
FieldTypeDescription
reasonstringDecline 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.
{
  "status": "reversed",
  "transactionId": "txn_abc123",
  "cardId": "card_xyz789",
  "amount": 5000,
  "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.
{
  "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
}
FieldTypeDescription
reversalTypestring"reversal", "partial_reversal", "refund", or "partial_refund"
originalAmountnumberOriginal authorized or settled amount in minor units (cents)
amountnumberThe reversed/refunded amount in minor units (cents)
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.
Status codeMeaning
200Event received and processed
202Event received, will process asynchronously
Any non-2xxDelivery failed — will retry

Example handler

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");
});