> ## Documentation Index
> Fetch the complete documentation index at: https://ramps-09-11-simplify-max-spend-per-transaction-descriptions.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Card webhook events and how to consume them

Cards add five webhook event types on top of Grid's existing webhook
infrastructure. Signature verification (`X-Grid-Signature`) and
retry behavior are identical to the rest of Grid — see
[Authentication](/api-reference/authentication) and
[Webhooks](/api-reference/webhooks) for the underlying mechanics.

One covers the card itself; the others cover a card transaction's
lifecycle.

## Event types

| Type                                 | Fires on                                                                                                                             |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ |
| `CARD.STATE_CHANGE`                  | `PROCESSING → ACTIVE`, `→ CLOSED (ISSUER_REJECTED)`, and every subsequent `ACTIVE ⇄ FROZEN` and `→ CLOSED` transition.               |
| `CARD_TRANSACTION.AUTHORIZED`        | An authorization is approved and a hold is placed on the funding source.                                                             |
| `CARD_TRANSACTION.PARTIALLY_SETTLED` | A clearing posted, but more are still expected.                                                                                      |
| `CARD_TRANSACTION.SETTLED`           | All clearings have posted. Also fires for a merchant return, which posts as its own `CREDIT` row linked via `originalTransactionId`. |
| `CARD_TRANSACTION.DECLINED`          | The authorization was declined before any money moved.                                                                               |
| `CARD_TRANSACTION.EXCEPTION`         | The transaction settled to the network but the pull from the funding source failed.                                                  |

All of them carry the standard envelope:

```json theme={null}
{
  "id": "Webhook:019542f5-b3e7-1d02-0000-000000000020",
  "type": "CARD.STATE_CHANGE",
  "timestamp": "2026-05-08T14:11:00Z",
  "data": { /* the affected Card or CardTransaction resource */ }
}
```

The `id` is unique per delivery and safe to use for idempotency.

## CARD.STATE\_CHANGE

The `data` payload is the post-change `Card` resource. Example —
activation after issuance:

```json theme={null}
{
  "id": "Webhook:019542f5-b3e7-1d02-0000-000000000020",
  "type": "CARD.STATE_CHANGE",
  "timestamp": "2026-05-08T14:11:00Z",
  "data": {
    "id": "Card:019542f5-b3e7-1d02-0000-000000000010",
    "customerId": "Customer:019542f5-b3e7-1d02-0000-000000000001",
    "state": "ACTIVE",
    "brand": "VISA",
    "form": "VIRTUAL",
    "last4": "4242",
    "expMonth": 12,
    "expYear": 2029,
    "fundingSource": "InternalAccount:019542f5-b3e7-1d02-0000-000000000002",
    "currency": "USD",
    "processorRef": "card_b81c2a4f",
    "issuerRef": "lead_card_7a1b9c3d",
    "createdAt": "2026-05-08T14:10:00Z",
    "updatedAt": "2026-05-08T14:11:00Z"
  }
}
```

Common branches to handle in your consumer:

* `state: "ACTIVE"` after `PROCESSING` — the card is live. To reveal
  the full card details, request a reveal with
  `POST /cards/{id}/reveal` right before rendering its short-lived
  `panEmbedUrl` in an iframe — webhook payloads never carry a reveal
  URL.
* `state: "CLOSED"`, `stateReason: "ISSUER_REJECTED"` — the issuer
  rejected provisioning; offer to issue a new card.
* `state: "FROZEN"` / `state: "ACTIVE"` — reflect the freeze toggle in
  your UI.
* `state: "CLOSED"`, `stateReason: "CLOSED_BY_PLATFORM"` — close
  confirmed; stop showing the card.

## Card-transaction lifecycle

The `data` payload is the whole post-change `CardTransaction` resource,
the same shape `GET /transactions` returns for a `CARD` row, so you can
upsert it by `data.id` without a follow-up read. Example — authorization
approved:

```json theme={null}
{
  "id": "Webhook:019542f5-b3e7-1d02-0000-000000000040",
  "type": "CARD_TRANSACTION.AUTHORIZED",
  "timestamp": "2026-05-09T10:00:00Z",
  "data": {
    "type": "CARD",
    "id": "Transaction:019542f5-b3e7-1d02-0000-000000000100",
    "cardId": "Card:019542f5-b3e7-1d02-0000-000000000010",
    "customerId": "Customer:019542f5-b3e7-1d02-0000-000000000001",
    "platformCustomerId": "18d3e5f7b4a9c2",
    "issuerTransactionToken": "lithic_txn_b81c2a4f",
    "status": "AUTHORIZED",
    "direction": "DEBIT",
    "merchant": {
      "descriptor": "BLUE BOTTLE COFFEE SF",
      "mcc": "5814",
      "country": "US"
    },
    "authorizedAmount": {
      "amount": 12550,
      "currency": {
        "code": "USD",
        "name": "United States Dollar",
        "symbol": "$",
        "decimals": 2
      }
    },
    "accountId": "InternalAccount:019542f5-b3e7-1d02-0000-000000000002",
    "authorizedAt": "2026-05-09T10:00:00Z",
    "createdAt": "2026-05-09T10:00:00Z",
    "updatedAt": "2026-05-09T10:00:00Z"
  }
}
```

Later deliveries for the same `data.id` carry the updated resource:
`settledAmount` appears once a clearing posts, and `status` moves to
`PARTIALLY_SETTLED`, `SETTLED`, `DECLINED`, or `EXCEPTION`. A merchant
return has no event type of its own — it arrives as a
`CARD_TRANSACTION.SETTLED` delivery for a new `data.id` with
`direction: "CREDIT"` and `originalTransactionId` pointing at the
purchase, so key your handling on `data.id` rather than on the event
type. See [Reconciliation](/cards/transactions/reconciliation) for the
underlying event model.

## Idempotency & retries

Webhook deliveries are at-least-once. Track processed `id` values and
return `200` on duplicates, or return `409` and let Grid stop
retrying. Both shapes are accepted by Grid's webhook infrastructure.
