Skip to content
Draft
5 changes: 5 additions & 0 deletions .redocly.lint-ignore.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,8 @@ openapi.yaml:
- '#/paths/~1sandbox~1cards~1{id}~1simulate~1authorization_advice'
- '#/paths/~1sandbox~1cards~1{id}~1simulate~1credit_authorization_advice'
- '#/paths/~1sandbox~1cards~1{id}~1simulate~1return_reversal'
# threeDSecurePassword: "threeDSecure" is the industry-standard casing for
# 3DS and matches the EU provider's wire field; the camelCase checker
# rejects the consecutive-capital hump. Deliberate exception.
field-names-camelCase:
- '#/components/schemas/CardCreateRequest/properties/threeDSecurePassword'
13 changes: 12 additions & 1 deletion .spectral.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ rules:
# Discriminators and Polymorphism (README: OpenAPI Best Practices)
# ============================================================

# oneOf must include a discriminator (anywhere it appears top-level
# oneOf must include a discriminator (anywhere it appears - top-level
# schemas, properties, request/response bodies). oneOf implies a tagged
# union; for nullable values use anyOf instead.
oneOf-must-have-discriminator:
Expand Down Expand Up @@ -179,3 +179,14 @@ rules:
function: pattern
functionOptions:
match: "^((\\/([a-z0-9]+(-[a-z0-9]+)*|\\{[a-zA-Z0-9]+\\}))+|\\/sandbox\\/cards\\/\\{id\\}\\/simulate\\/(balance_inquiry|credit_authorization|financial_authorization|financial_credit_authorization|authorization_advice|credit_authorization_advice|return_reversal))$"

# Per-pointer exceptions (mirror .redocly.lint-ignore.yaml - both linters run
# the same conventions, and spectral has no ignore-file mechanism).
overrides:
# threeDSecurePassword: "threeDSecure" is the industry-standard casing for
# 3DS and matches the EU provider's wire field; the camelCase checker
# rejects the consecutive-capital hump. Deliberate exception.
- files:
- "**/openapi.yaml#/components/schemas/CardCreateRequest/properties/threeDSecurePassword"
rules:
field-names-camelCase: off
29 changes: 26 additions & 3 deletions mintlify/openapi.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 6 additions & 9 deletions mintlify/snippets/cards/implementation-overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,8 @@ cards. Cards do not require new webhook endpoints or new API
credentials — they reuse what's already configured for the rest of
Grid. You'll only need to:

- Subscribe to the new card-specific webhook types (`CARD.STATE_CHANGE`
and `CARD.FUNDING_SOURCE_CHANGE`). Card-transaction lifecycle events
ride on the generic transaction webhook stream that already covers
outgoing-payment activity.
- Subscribe to the new card-specific webhook types (`CARD.STATE_CHANGE`,
`CARD.FUNDING_SOURCE_CHANGE`, and the `CARD_TRANSACTION.*` family).
- Confirm with your Lightspark contact that cards are enabled for your
platform — issuance requires an issuer-side onboarding.

Expand Down Expand Up @@ -88,11 +86,10 @@ When you're ready to go live:

- Complete card-issuer onboarding through your Lightspark contact.
- Confirm webhook security, monitoring, and alerting cover the
`CARD.*` event types plus card-destination transactions on the
generic transaction webhook stream.
- Build the `EXCEPTION` dashboard view from card-destination
transaction webhooks (filter by `status: "EXCEPTION"`) and wire it
into on-call alerting.
`CARD.*` and `CARD_TRANSACTION.*` event types.
- Build the `EXCEPTION` dashboard view from
`CARD_TRANSACTION.EXCEPTION` webhooks and wire it into on-call
alerting.

<Check>
Contact your Lightspark representative to enable Production card
Expand Down
6 changes: 3 additions & 3 deletions mintlify/snippets/cards/quickstart.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,9 @@ curl -X POST "$GRID_BASE_URL/sandbox/cards/Card:019542f5-b3e7-1d02-0000-00000000
}'
```

A transaction webhook fires with the updated parent: the transaction
moves to `SETTLED`, `pullSummary.count` becomes `2`, and
`settlementSummary.totalAmount` is `1500`.
A `CARD_TRANSACTION.SETTLED` webhook fires with the updated parent:
the transaction moves to `SETTLED`, `pullSummary.count` becomes `2`,
and `settlementSummary.totalAmount` is `1500`.

The webhook payload carries the full parent `CardTransaction` with
child pull, clearing, and refund events rolled up into the
Expand Down
49 changes: 30 additions & 19 deletions mintlify/snippets/cards/reconciliation.mdx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
A card transaction is not a single event it's a parent row plus a
A card transaction is not a single event - it's a parent row plus a
stream of child events from the card network. This page covers the
event model, the status transitions, and how to handle the
`EXCEPTION` path.
Expand All @@ -7,16 +7,16 @@ event model, the status transitions, and how to handle the

For each card authorization, Grid produces:

1. **One parent `CardTransaction`** created at auth time, persists for
1. **One parent `CardTransaction`** - created at auth time, persists for
the life of the transaction.
2. **Pulls** debits against the funding source that fund approved
2. **Pulls** - debits against the funding source that fund approved
auths and any post-hoc settlements.
3. **Clearings** the network's confirmation that funds have moved.
4. **Refunds** merchant-initiated `RETURN` events.
3. **Clearings** - the network's confirmation that funds have moved.
4. **Refunds** - merchant-initiated `RETURN` events.

Children are reconciled against the parent and rolled up into three
aggregates: `pullSummary`, `settlementSummary`, and `refundSummary`.
You don't see per-child rows on the list endpoint they're summarized
You don't see per-child rows on the list endpoint - they're summarized
on the parent.

## Status transitions
Expand All @@ -32,15 +32,26 @@ AUTHORIZED ──► PARTIALLY_SETTLED ──► SETTLED ──► REFUNDED
| Status | Meaning |
|--------|---------|
| `AUTHORIZED` | Auth approved, hold placed, no clearings yet. |
| `DECLINED` | The authorization was declined by the issuer or program. No funds moved; the transaction is terminal. |
| `PARTIALLY_SETTLED` | At least one clearing landed, but more are still expected (split shipments, multi-leg trips). |
| `SETTLED` | All clearings for the auth have posted. The transaction is closed against the funding source. |
| `REFUNDED` | A `RETURN` was received and the net settled amount has been refunded in full or part. |
| `EXCEPTION` | The transaction settled to the network but the corresponding pull from the funding source failed. |

Every transition is delivered via the generic transaction webhook
stream carrying the post-change parent (a follow-up extends the
Transaction model with a card destination type — see
[Webhooks](/cards/platform-tools/webhooks)).
Every state transition is delivered via a `CARD_TRANSACTION.*` webhook
whose type names the new status (`CARD_TRANSACTION.AUTHORIZED`,
`CARD_TRANSACTION.SETTLED`, ...) and carries the post-update parent.
See [Webhooks](/cards/platform-tools/webhooks) for the full payload
shape.

**Refund linkage differs by card program.** US programs link merchant
refunds to the original purchase: the purchase transitions to `REFUNDED`
and its `refundedAmount`/`refundSummary` update. EU programs receive
merchant refunds from the network WITHOUT a purchase reference, so a
refund arrives as a **standalone CREDIT-direction card transaction** (its
own `CARD_TRANSACTION.*` lifecycle) and the original purchase keeps its
final status with `refundedAmount` unset. Reconcile EU refunds by
merchant/amount/time rather than by parent linkage.

## The over-auth path

Expand All @@ -59,13 +70,13 @@ The post-settlement parent carries `authorizedAmount: 1250`,

An exception happens when the card network has already moved funds for
a settlement but Grid can't pull the matching amount from the funding
source typically because the cardholder's balance no longer covers
source - typically because the cardholder's balance no longer covers
the post-hoc difference.

Signal to watch: a transaction webhook with `status: "EXCEPTION"` for
a card-destination transaction. The payload includes the full parent
record, so your dashboard's exception view is driven entirely by
webhook deliveries — there's no list endpoint to poll.
Signal to watch: a `CARD_TRANSACTION.EXCEPTION` webhook. The payload
includes the full parent record, so your dashboard's exception view is
driven entirely by webhook deliveries - there's no list endpoint to
poll.

Exceptions don't roll back automatically. The standard response is to
top up the funding source (or move the customer to a state where their
Expand All @@ -74,10 +85,10 @@ exception to resolution.

## Idempotency on webhooks

Every transaction webhook carries a unique `id`. Track processed
webhook IDs and treat duplicates as no-ops Grid retries failed
deliveries, and your reconciliation should be safe under at-least-once
delivery.
Every `CARD_TRANSACTION.*` webhook carries a unique `id`. Track
processed webhook IDs and treat duplicates as no-ops - Grid retries
failed deliveries, and your reconciliation should be safe under
at-least-once delivery.

See [Webhooks](/cards/platform-tools/webhooks) for signature
verification and the full payload shape.
4 changes: 2 additions & 2 deletions mintlify/snippets/cards/sandbox-testing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ curl -X POST "$GRID_BASE_URL/sandbox/cards/Card:.../simulate/return" \
-d '{ "cardTransactionId": "CardTransaction:...", "amount": 1500 }'
```

At each step you'll see `CARD.STATE_CHANGE` or
`CARD.FUNDING_SOURCE_CHANGE` webhooks plus transaction webhooks for
At each step you'll see `CARD.STATE_CHANGE`,
`CARD.FUNDING_SOURCE_CHANGE`, or `CARD_TRANSACTION.*` webhooks for
the simulated authorization, clearing, and return — wire those into
your local webhook handler to validate end-to-end.
80 changes: 66 additions & 14 deletions mintlify/snippets/cards/webhooks.mdx
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
Cards add two webhook event types on top of Grid's existing webhook
Cards add two card-lifecycle webhook event types plus the
`CARD_TRANSACTION.*` family 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.

Card-transaction lifecycle events are not card-specific webhooks —
they ride on the generic transaction webhook stream (a follow-up
extends the Transaction model with a card destination type).

## Event types

| Type | Fires on |
|------|----------|
| `CARD.STATE_CHANGE` | `PROCESSING → ACTIVE`, `→ CLOSED (ISSUER_REJECTED)`, and every subsequent `ACTIVE ⇄ FROZEN` and `→ CLOSED` transition. |
| `CARD.FUNDING_SOURCE_CHANGE` | Whenever `PATCH /cards/{id}` updates the `fundingSources` array. |
| `CARD_TRANSACTION.AUTHORIZED` | An authorization is approved and the parent `CardTransaction` is created. |
| `CARD_TRANSACTION.DECLINED` | An authorization is declined; the transaction is terminal and no funds moved. |
| `CARD_TRANSACTION.PARTIALLY_SETTLED` | A clearing posted but more are still expected. |
| `CARD_TRANSACTION.SETTLED` | All clearings for the auth have posted. |
| `CARD_TRANSACTION.REFUNDED` | A merchant `RETURN` refunded the net settled amount in part or whole. |
| `CARD_TRANSACTION.EXCEPTION` | The transaction settled to the network but the funding-source pull failed. |

All three carry the standard envelope:
All of them carry the standard envelope:

```json
{
Expand Down Expand Up @@ -80,15 +83,64 @@ array. The `data` payload is the full `Card` resource with the
post-change `fundingSources`, so a consumer that only cares about the
current set of bindings can replace state wholesale.

## Card-transaction lifecycle
## The CARD_TRANSACTION.* family

Fires on every state transition of a `CardTransaction` — the webhook

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that this event only fires on state transitions and the type itself names the new status, how do we deliver reconciliation updates that don't change CardTransaction.status — e.g. a partial refund that stays SETTLED, or another partial clearing? Is there still a channel for those, or is that intentionally out of scope here?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: since this family fires only on state transitions, does a same-status mutation get its own event — e.g. another partial clearing while still PARTIALLY_SETTLED, or a second partial return while REFUNDED? If not, could pullSummary/refundSummary/settlementSummary go stale between transitions for consumers relying on these webhooks for reconciliation?

type names the transaction's new status, so consumers can route purely
on `type` without inspecting `data.status`. The `data` payload is the
full post-transition `CardTransaction`, including its lifecycle status
and reconciliation aggregates.

```json
{
"id": "Webhook:019542f5-b3e7-1d02-0000-000000000040",
"type": "CARD_TRANSACTION.SETTLED",
"timestamp": "2026-05-08T15:42:11Z",
"data": {
"type": "CARD",
"id": "CardTransaction:019542f5-b3e7-1d02-0000-000000000100",
"cardId": "Card:019542f5-b3e7-1d02-0000-000000000010",
"customerId": "Customer:019542f5-b3e7-1d02-0000-000000000001",
"platformCustomerId": "18d3e5f7b4a9c2",
"status": "SETTLED",
"direction": "DEBIT",
"merchant": {
"descriptor": "BLUE BOTTLE COFFEE SF",
"mcc": "5814",
"country": "US"
},
"authorizedAmount": {
"amount": 1250,
"currency": { "code": "USD" }
},
"settledAmount": {
"amount": 1500,
"currency": { "code": "USD" }
},
"accountId": "InternalAccount:019542f5-b3e7-1d02-0000-000000000002",
"pullSummary": {
"count": 2,
"totalAmount": 1500,
"pendingCount": 0
},
"refundSummary": {
"count": 0,
"totalAmount": 0
},
"settlementSummary": {
"count": 1,
"totalAmount": 1500
},
"authorizedAt": "2026-05-08T14:30:00Z",
"lastEventAt": "2026-05-08T15:42:11Z",
"createdAt": "2026-05-08T14:30:00Z",
"updatedAt": "2026-05-08T15:42:11Z"
}
}
```

Authorization, pull, clearing, refund, and `EXCEPTION` transitions are
not delivered through a dedicated card webhook. They flow through
the generic transaction webhook stream that already carries
outgoing-payment lifecycle events; a follow-up PR adds the card
destination type to that stream. See
[Reconciliation](/cards/transactions/reconciliation) for the
underlying event model.
See [Reconciliation](/cards/transactions/reconciliation) for the
underlying event model and `EXCEPTION` handling.

## Idempotency & retries

Expand Down
Loading
Loading