From 782f51f54b6f71c785510209450a34df1491235e Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 9 Sep 2026 19:03:15 +0000 Subject: [PATCH 1/3] remove the CARD.FUNDING_SOURCE_CHANGE webhook from the spec Grid does not send a webhook when a card's funding sources change. The PATCH response and GET /cards/{id} already return the new list, so the webhook, its schema, the enum value, and the docs that promised it are removed. Co-Authored-By: Claude Fable 5.1 --- .../skills/grid-api/references/endpoints.md | 1 - .stainless/stainless.yml | 2 +- mintlify/openapi.yaml | 94 +------------------ mintlify/snippets/cards/funding-sources.mdx | 5 +- .../cards/implementation-overview.mdx | 4 +- mintlify/snippets/cards/sandbox-testing.mdx | 5 +- mintlify/snippets/cards/webhooks.mdx | 12 +-- openapi.yaml | 94 +------------------ .../CardFundingSourceChangeWebhook.yaml | 12 --- .../schemas/webhooks/WebhookType.yaml | 1 - openapi/openapi.yaml | 2 - openapi/paths/cards/cards_{id}.yaml | 7 +- .../webhooks/card-funding-source-change.yaml | 89 ------------------ 13 files changed, 17 insertions(+), 311 deletions(-) delete mode 100644 openapi/components/schemas/webhooks/CardFundingSourceChangeWebhook.yaml delete mode 100644 openapi/webhooks/card-funding-source-change.yaml diff --git a/.claude/skills/grid-api/references/endpoints.md b/.claude/skills/grid-api/references/endpoints.md index 977891c24..95ef76828 100644 --- a/.claude/skills/grid-api/references/endpoints.md +++ b/.claude/skills/grid-api/references/endpoints.md @@ -332,7 +332,6 @@ Drive card lifecycle events in sandbox to test authorization, clearing, and retu - `verification-update`: Verification status change - `agent-action`: Agent action requires approval or changed status - `card-state-change`: Card state changed -- `card-funding-source-change`: Card funding source changed - `card-transaction`: Card transaction event - `bulk-upload`: Bulk job completion - `invitation-claimed`: Invitation claimed diff --git a/.stainless/stainless.yml b/.stainless/stainless.yml index affc639f3..cdf15d245 100644 --- a/.stainless/stainless.yml +++ b/.stainless/stainless.yml @@ -1028,7 +1028,7 @@ openapi: reason: >- Remove the wide WebhookType $ref from BaseWebhook so the inline single-value enums on each child webhook (e.g. CardStateChangeWebhook, - CardFundingSourceChangeWebhook) become the sole definition. Without + CardTransactionWebhook) become the sole definition. Without this, every generated *WebhookEvent has the full WebhookType enum on its `type` field, so the UnwrapWebhookEvent deserializer can't discriminate between variants that share a `data` shape (Card) and diff --git a/mintlify/openapi.yaml b/mintlify/openapi.yaml index d04857649..7bdc2d870 100644 --- a/mintlify/openapi.yaml +++ b/mintlify/openapi.yaml @@ -8771,9 +8771,9 @@ paths: - `state: FROZEN`: Authorization Decisioning declines new auths with `CARD_PAUSED`. Existing pulls and in-flight reconciliation continue — freezing does not pause the lifecycle of authorizations that already passed. - `state: ACTIVE`: normal authorization behavior resumes. - `state: CLOSED`: terminal close. The card transitions to `state: "CLOSED"` with `stateReason: "CLOSED_BY_PLATFORM"` and stays in the system for audit and reconciliation. All pending auths reconcile to a terminal state via the existing reconcile primitive. Inbound clearings received after close follow the standard force-post / late-presentment path — Lightspark absorbs the loss if a post-hoc pull on the now-unbound source fails. Funding-source bindings are detached. Refunds already in flight still complete because Lightspark holds the card-reserve keys. - - `fundingSources` change: emits `card.funding_source_change` reflecting the new ordered binding. + - `fundingSources` change: replaces the ordered binding. No webhook fires for this; read the card back to confirm the new list. - The `card.state_change` webhook fires on every successful `state` transition; the `card.funding_source_change` webhook fires whenever `fundingSources` is updated. + The `card.state_change` webhook fires on every successful `state` transition. operationId: updateCardById tags: - Cards @@ -11752,82 +11752,6 @@ webhooks: application/json: schema: $ref: '#/components/schemas/Error409' - card-funding-source-change: - post: - summary: Card funding source change - description: | - Webhook that is called when the funding sources bound to a card change. Fires whenever `PATCH /cards/{id}` updates the `fundingSources` array. The payload carries the full `Card` resource with the post-change `fundingSources` array. - - This endpoint should be implemented by clients of the Grid API. - - ### Authentication - - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash - - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - operationId: cardFundingSourceChangeWebhook - tags: - - Webhooks - security: - - WebhookSignature: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CardFundingSourceChangeWebhook' - examples: - fundingSourcesReplaced: - summary: Funding sources replaced via PATCH /cards/{id} - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000030 - type: CARD.FUNDING_SOURCE_CHANGE - timestamp: '2026-05-08T14:30:00Z' - data: - id: Card:019542f5-b3e7-1d02-0000-000000000010 - cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 - state: ACTIVE - stateReason: null - brand: VISA - form: VIRTUAL - last4: '4242' - expMonth: 12 - expYear: 2029 - fundingSources: - - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - - InternalAccount:019542f5-b3e7-1d02-0000-000000000003 - maxSpendPerTransaction: null - maxSpendPerDay: null - currency: USD - createdAt: '2026-05-08T14:10:00Z' - updatedAt: '2026-05-08T14:30:00Z' - responses: - '200': - description: | - Webhook received successfully - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - Signature validation failed - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '409': - description: Conflict - Webhook has already been processed (duplicate id) - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' card-transaction: post: summary: Card transaction @@ -27251,7 +27175,6 @@ components: - BULK_UPLOAD.FAILED - AGENT_ACTION.PENDING_APPROVAL - CARD.STATE_CHANGE - - CARD.FUNDING_SOURCE_CHANGE - CARD_TRANSACTION.AUTHORIZED - CARD_TRANSACTION.PARTIALLY_SETTLED - CARD_TRANSACTION.SETTLED @@ -27484,19 +27407,6 @@ components: type: string enum: - CARD.STATE_CHANGE - CardFundingSourceChangeWebhook: - allOf: - - $ref: '#/components/schemas/BaseWebhook' - - type: object - required: - - data - properties: - data: - $ref: '#/components/schemas/Card' - type: - type: string - enum: - - CARD.FUNDING_SOURCE_CHANGE CardTransactionWebhook: allOf: - $ref: '#/components/schemas/BaseWebhook' diff --git a/mintlify/snippets/cards/funding-sources.mdx b/mintlify/snippets/cards/funding-sources.mdx index 1653289e6..edf6f62ca 100644 --- a/mintlify/snippets/cards/funding-sources.mdx +++ b/mintlify/snippets/cards/funding-sources.mdx @@ -55,8 +55,9 @@ curl -X PATCH "$GRID_BASE_URL/cards/Card:019542f5-b3e7-1d02-0000-000000000010" \ The same flow covers `state`, `fundingSources`, or both fields supplied together. -`CARD.FUNDING_SOURCE_CHANGE` fires on every successful update with the -post-change `Card` resource. +No webhook fires for a funding source change. The `200` response to the +signed retry carries the post-change `Card` resource, and `GET /cards/{id}` +returns the current list at any time. ### Errors diff --git a/mintlify/snippets/cards/implementation-overview.mdx b/mintlify/snippets/cards/implementation-overview.mdx index 84c52b9a8..06ee8fb78 100644 --- a/mintlify/snippets/cards/implementation-overview.mdx +++ b/mintlify/snippets/cards/implementation-overview.mdx @@ -15,8 +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 +- Subscribe to the new card-specific webhook type (`CARD.STATE_CHANGE`). + Card-transaction lifecycle events ride on the generic transaction webhook stream that already covers outgoing-payment activity. - Confirm with your Lightspark contact that cards are enabled for your diff --git a/mintlify/snippets/cards/sandbox-testing.mdx b/mintlify/snippets/cards/sandbox-testing.mdx index 45a0c286a..83bd27ad0 100644 --- a/mintlify/snippets/cards/sandbox-testing.mdx +++ b/mintlify/snippets/cards/sandbox-testing.mdx @@ -139,7 +139,6 @@ 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 -the simulated authorization, clearing, and return — wire those into +At each step you'll see `CARD.STATE_CHANGE` webhooks plus transaction +webhooks for the simulated authorization, clearing, and return — wire those into your local webhook handler to validate end-to-end. diff --git a/mintlify/snippets/cards/webhooks.mdx b/mintlify/snippets/cards/webhooks.mdx index c3318d863..f563b9d5f 100644 --- a/mintlify/snippets/cards/webhooks.mdx +++ b/mintlify/snippets/cards/webhooks.mdx @@ -1,4 +1,4 @@ -Cards add two webhook event types on top of Grid's existing webhook +Cards add one webhook event type 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 @@ -13,9 +13,8 @@ extends the Transaction model with a card destination type). | 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. | -All three carry the standard envelope: +It carries the standard envelope: ```json { @@ -73,13 +72,6 @@ Common branches to handle in your consumer: - `state: "CLOSED"`, `stateReason: "CLOSED_BY_PLATFORM"` — close confirmed; stop showing the card. -## CARD.FUNDING_SOURCE_CHANGE - -Fires whenever a `PATCH /cards/{id}` call changes the `fundingSources` -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 Authorization, pull, clearing, refund, and `EXCEPTION` transitions are diff --git a/openapi.yaml b/openapi.yaml index d04857649..7bdc2d870 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -8771,9 +8771,9 @@ paths: - `state: FROZEN`: Authorization Decisioning declines new auths with `CARD_PAUSED`. Existing pulls and in-flight reconciliation continue — freezing does not pause the lifecycle of authorizations that already passed. - `state: ACTIVE`: normal authorization behavior resumes. - `state: CLOSED`: terminal close. The card transitions to `state: "CLOSED"` with `stateReason: "CLOSED_BY_PLATFORM"` and stays in the system for audit and reconciliation. All pending auths reconcile to a terminal state via the existing reconcile primitive. Inbound clearings received after close follow the standard force-post / late-presentment path — Lightspark absorbs the loss if a post-hoc pull on the now-unbound source fails. Funding-source bindings are detached. Refunds already in flight still complete because Lightspark holds the card-reserve keys. - - `fundingSources` change: emits `card.funding_source_change` reflecting the new ordered binding. + - `fundingSources` change: replaces the ordered binding. No webhook fires for this; read the card back to confirm the new list. - The `card.state_change` webhook fires on every successful `state` transition; the `card.funding_source_change` webhook fires whenever `fundingSources` is updated. + The `card.state_change` webhook fires on every successful `state` transition. operationId: updateCardById tags: - Cards @@ -11752,82 +11752,6 @@ webhooks: application/json: schema: $ref: '#/components/schemas/Error409' - card-funding-source-change: - post: - summary: Card funding source change - description: | - Webhook that is called when the funding sources bound to a card change. Fires whenever `PATCH /cards/{id}` updates the `fundingSources` array. The payload carries the full `Card` resource with the post-change `fundingSources` array. - - This endpoint should be implemented by clients of the Grid API. - - ### Authentication - - The webhook includes a signature in the `X-Grid-Signature` header that allows you to verify that the webhook was sent by Grid. - To verify the signature: - 1. Get the Grid public key provided to you during integration - 2. Decode the base64 signature from the header - 3. Create a SHA-256 hash of the request body - 4. Verify the signature using the public key and the hash - - If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - operationId: cardFundingSourceChangeWebhook - tags: - - Webhooks - security: - - WebhookSignature: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CardFundingSourceChangeWebhook' - examples: - fundingSourcesReplaced: - summary: Funding sources replaced via PATCH /cards/{id} - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000030 - type: CARD.FUNDING_SOURCE_CHANGE - timestamp: '2026-05-08T14:30:00Z' - data: - id: Card:019542f5-b3e7-1d02-0000-000000000010 - cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 - state: ACTIVE - stateReason: null - brand: VISA - form: VIRTUAL - last4: '4242' - expMonth: 12 - expYear: 2029 - fundingSources: - - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - - InternalAccount:019542f5-b3e7-1d02-0000-000000000003 - maxSpendPerTransaction: null - maxSpendPerDay: null - currency: USD - createdAt: '2026-05-08T14:10:00Z' - updatedAt: '2026-05-08T14:30:00Z' - responses: - '200': - description: | - Webhook received successfully - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - Signature validation failed - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '409': - description: Conflict - Webhook has already been processed (duplicate id) - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' card-transaction: post: summary: Card transaction @@ -27251,7 +27175,6 @@ components: - BULK_UPLOAD.FAILED - AGENT_ACTION.PENDING_APPROVAL - CARD.STATE_CHANGE - - CARD.FUNDING_SOURCE_CHANGE - CARD_TRANSACTION.AUTHORIZED - CARD_TRANSACTION.PARTIALLY_SETTLED - CARD_TRANSACTION.SETTLED @@ -27484,19 +27407,6 @@ components: type: string enum: - CARD.STATE_CHANGE - CardFundingSourceChangeWebhook: - allOf: - - $ref: '#/components/schemas/BaseWebhook' - - type: object - required: - - data - properties: - data: - $ref: '#/components/schemas/Card' - type: - type: string - enum: - - CARD.FUNDING_SOURCE_CHANGE CardTransactionWebhook: allOf: - $ref: '#/components/schemas/BaseWebhook' diff --git a/openapi/components/schemas/webhooks/CardFundingSourceChangeWebhook.yaml b/openapi/components/schemas/webhooks/CardFundingSourceChangeWebhook.yaml deleted file mode 100644 index a2d6da26e..000000000 --- a/openapi/components/schemas/webhooks/CardFundingSourceChangeWebhook.yaml +++ /dev/null @@ -1,12 +0,0 @@ -allOf: - - $ref: ./BaseWebhook.yaml - - type: object - required: - - data - properties: - data: - $ref: ../cards/Card.yaml - type: - type: string - enum: - - CARD.FUNDING_SOURCE_CHANGE diff --git a/openapi/components/schemas/webhooks/WebhookType.yaml b/openapi/components/schemas/webhooks/WebhookType.yaml index c16199b7d..81d244c76 100644 --- a/openapi/components/schemas/webhooks/WebhookType.yaml +++ b/openapi/components/schemas/webhooks/WebhookType.yaml @@ -37,7 +37,6 @@ enum: - BULK_UPLOAD.FAILED - AGENT_ACTION.PENDING_APPROVAL - CARD.STATE_CHANGE - - CARD.FUNDING_SOURCE_CHANGE - CARD_TRANSACTION.AUTHORIZED - CARD_TRANSACTION.PARTIALLY_SETTLED - CARD_TRANSACTION.SETTLED diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 7b5b496ec..af4b22122 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -413,8 +413,6 @@ webhooks: $ref: webhooks/verification-update.yaml card-state-change: $ref: webhooks/card-state-change.yaml - card-funding-source-change: - $ref: webhooks/card-funding-source-change.yaml card-transaction: $ref: webhooks/card-transaction.yaml wallet-operation: diff --git a/openapi/paths/cards/cards_{id}.yaml b/openapi/paths/cards/cards_{id}.yaml index 7b64655bd..fbbcb086b 100644 --- a/openapi/paths/cards/cards_{id}.yaml +++ b/openapi/paths/cards/cards_{id}.yaml @@ -119,13 +119,12 @@ patch: bindings are detached. Refunds already in flight still complete because Lightspark holds the card-reserve keys. - - `fundingSources` change: emits `card.funding_source_change` reflecting - the new ordered binding. + - `fundingSources` change: replaces the ordered binding. No webhook + fires for this; read the card back to confirm the new list. The `card.state_change` webhook fires on every successful `state` - transition; the `card.funding_source_change` webhook fires whenever - `fundingSources` is updated. + transition. operationId: updateCardById tags: - Cards diff --git a/openapi/webhooks/card-funding-source-change.yaml b/openapi/webhooks/card-funding-source-change.yaml deleted file mode 100644 index 32fd780fd..000000000 --- a/openapi/webhooks/card-funding-source-change.yaml +++ /dev/null @@ -1,89 +0,0 @@ -post: - summary: Card funding source change - description: > - Webhook that is called when the funding sources bound to a card change. - Fires whenever `PATCH /cards/{id}` updates the `fundingSources` array. - The payload carries the full `Card` resource with the post-change - `fundingSources` array. - - - This endpoint should be implemented by clients of the Grid API. - - - ### Authentication - - - The webhook includes a signature in the `X-Grid-Signature` header that - allows you to verify that the webhook was sent by Grid. - - To verify the signature: - - 1. Get the Grid public key provided to you during integration - - 2. Decode the base64 signature from the header - - 3. Create a SHA-256 hash of the request body - - 4. Verify the signature using the public key and the hash - - - If the signature verification succeeds, the webhook is authentic. If not, it - should be rejected. - operationId: cardFundingSourceChangeWebhook - tags: - - Webhooks - security: - - WebhookSignature: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: ../components/schemas/webhooks/CardFundingSourceChangeWebhook.yaml - examples: - fundingSourcesReplaced: - summary: Funding sources replaced via PATCH /cards/{id} - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000030 - type: CARD.FUNDING_SOURCE_CHANGE - timestamp: '2026-05-08T14:30:00Z' - data: - id: Card:019542f5-b3e7-1d02-0000-000000000010 - cardholderId: Customer:019542f5-b3e7-1d02-0000-000000000001 - state: ACTIVE - stateReason: null - brand: VISA - form: VIRTUAL - last4: '4242' - expMonth: 12 - expYear: 2029 - fundingSources: - - InternalAccount:019542f5-b3e7-1d02-0000-000000000002 - - InternalAccount:019542f5-b3e7-1d02-0000-000000000003 - maxSpendPerTransaction: null - maxSpendPerDay: null - currency: USD - createdAt: '2026-05-08T14:10:00Z' - updatedAt: '2026-05-08T14:30:00Z' - responses: - '200': - description: > - Webhook received successfully - '400': - description: Bad request - content: - application/json: - schema: - $ref: ../components/schemas/errors/Error400.yaml - '401': - description: Unauthorized - Signature validation failed - content: - application/json: - schema: - $ref: ../components/schemas/errors/Error401.yaml - '409': - description: Conflict - Webhook has already been processed (duplicate id) - content: - application/json: - schema: - $ref: ../components/schemas/errors/Error409.yaml From 9d64c9ff16368ff76d6a2708a4897fd18b99ad0b Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 9 Sep 2026 19:06:58 +0000 Subject: [PATCH 2/3] docs: describe the card transaction webhooks as their own event types The cards docs said transaction lifecycle events ride on the generic transaction webhook stream and that a follow-up would add a card type. The spec has had dedicated CARD_TRANSACTION.* events for some time, so the docs now list them. Also drops the note about funding source changes. Co-Authored-By: Claude Fable 5.1 --- mintlify/snippets/cards/funding-sources.mdx | 4 ---- .../cards/implementation-overview.mdx | 15 ++++++------ mintlify/snippets/cards/reconciliation.mdx | 6 ++--- mintlify/snippets/cards/webhooks.mdx | 23 +++++++++++-------- 4 files changed, 22 insertions(+), 26 deletions(-) diff --git a/mintlify/snippets/cards/funding-sources.mdx b/mintlify/snippets/cards/funding-sources.mdx index edf6f62ca..eac705db7 100644 --- a/mintlify/snippets/cards/funding-sources.mdx +++ b/mintlify/snippets/cards/funding-sources.mdx @@ -55,10 +55,6 @@ curl -X PATCH "$GRID_BASE_URL/cards/Card:019542f5-b3e7-1d02-0000-000000000010" \ The same flow covers `state`, `fundingSources`, or both fields supplied together. -No webhook fires for a funding source change. The `200` response to the -signed retry carries the post-change `Card` resource, and `GET /cards/{id}` -returns the current list at any time. - ### Errors | Status | Code | What it means | diff --git a/mintlify/snippets/cards/implementation-overview.mdx b/mintlify/snippets/cards/implementation-overview.mdx index 06ee8fb78..0e8a4cb2a 100644 --- a/mintlify/snippets/cards/implementation-overview.mdx +++ b/mintlify/snippets/cards/implementation-overview.mdx @@ -15,10 +15,10 @@ 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 type (`CARD.STATE_CHANGE`). - Card-transaction lifecycle events - ride on the generic transaction webhook stream that already covers - outgoing-payment activity. +- Subscribe to the card webhook types: `CARD.STATE_CHANGE` for card + state, and `CARD_TRANSACTION.AUTHORIZED`, + `CARD_TRANSACTION.PARTIALLY_SETTLED`, `CARD_TRANSACTION.SETTLED`, and + `CARD_TRANSACTION.EXCEPTION` for the transaction lifecycle. - Confirm with your Lightspark contact that cards are enabled for your platform — issuance requires an issuer-side onboarding. - If your program includes physical cards, start the artwork early: @@ -93,10 +93,9 @@ 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 + `CARD.*` and `CARD_TRANSACTION.*` event types. +- Build the `EXCEPTION` dashboard view from `CARD_TRANSACTION.EXCEPTION` + webhooks and wire it into on-call alerting. diff --git a/mintlify/snippets/cards/reconciliation.mdx b/mintlify/snippets/cards/reconciliation.mdx index fffb119a7..b30ce5d15 100644 --- a/mintlify/snippets/cards/reconciliation.mdx +++ b/mintlify/snippets/cards/reconciliation.mdx @@ -37,10 +37,8 @@ A merchant `RETURN` does not move the status. The transaction stays | `SETTLED` | All clearings for the auth have posted. The transaction is closed against the funding source. A `RETURN` received afterwards keeps it `SETTLED` with the returned value in `refundedAmount`. | | `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 transition is delivered as a `CARD_TRANSACTION.*` webhook carrying +the post-change parent — see [Webhooks](/cards/platform-tools/webhooks). ## The over-auth path diff --git a/mintlify/snippets/cards/webhooks.mdx b/mintlify/snippets/cards/webhooks.mdx index f563b9d5f..9e51984c7 100644 --- a/mintlify/snippets/cards/webhooks.mdx +++ b/mintlify/snippets/cards/webhooks.mdx @@ -1,20 +1,23 @@ -Cards add one webhook event type on top of Grid's existing webhook +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. -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). +One covers the card itself; the other four 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. Re-fires when a merchant return lands, with the returned value in `refundedAmount`. | +| `CARD_TRANSACTION.EXCEPTION` | The transaction settled to the network but the pull from the funding source failed. | -It carries the standard envelope: +All of them carry the standard envelope: ```json { @@ -74,11 +77,11 @@ Common branches to handle in your consumer: ## Card-transaction lifecycle -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 +Each `CARD_TRANSACTION.*` event carries the full `CardTransaction` +resource. Not every delivery changes `status`: a merchant return has no +event type of its own and re-fires `CARD_TRANSACTION.SETTLED` with the +same status, so treat a repeated `SETTLED` as a new update and read +`refundedAmount` to tell it apart from the original settlement. See [Reconciliation](/cards/transactions/reconciliation) for the underlying event model. From f3e8554a608331e05f593ccec5665be7731a0dd2 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 9 Sep 2026 19:12:29 +0000 Subject: [PATCH 3/3] drop the fundingSources bullet from the PATCH card description Co-Authored-By: Claude Fable 5.1 --- mintlify/openapi.yaml | 1 - openapi.yaml | 1 - openapi/paths/cards/cards_{id}.yaml | 3 --- 3 files changed, 5 deletions(-) diff --git a/mintlify/openapi.yaml b/mintlify/openapi.yaml index 7bdc2d870..a876f9a54 100644 --- a/mintlify/openapi.yaml +++ b/mintlify/openapi.yaml @@ -8771,7 +8771,6 @@ paths: - `state: FROZEN`: Authorization Decisioning declines new auths with `CARD_PAUSED`. Existing pulls and in-flight reconciliation continue — freezing does not pause the lifecycle of authorizations that already passed. - `state: ACTIVE`: normal authorization behavior resumes. - `state: CLOSED`: terminal close. The card transitions to `state: "CLOSED"` with `stateReason: "CLOSED_BY_PLATFORM"` and stays in the system for audit and reconciliation. All pending auths reconcile to a terminal state via the existing reconcile primitive. Inbound clearings received after close follow the standard force-post / late-presentment path — Lightspark absorbs the loss if a post-hoc pull on the now-unbound source fails. Funding-source bindings are detached. Refunds already in flight still complete because Lightspark holds the card-reserve keys. - - `fundingSources` change: replaces the ordered binding. No webhook fires for this; read the card back to confirm the new list. The `card.state_change` webhook fires on every successful `state` transition. operationId: updateCardById diff --git a/openapi.yaml b/openapi.yaml index 7bdc2d870..a876f9a54 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -8771,7 +8771,6 @@ paths: - `state: FROZEN`: Authorization Decisioning declines new auths with `CARD_PAUSED`. Existing pulls and in-flight reconciliation continue — freezing does not pause the lifecycle of authorizations that already passed. - `state: ACTIVE`: normal authorization behavior resumes. - `state: CLOSED`: terminal close. The card transitions to `state: "CLOSED"` with `stateReason: "CLOSED_BY_PLATFORM"` and stays in the system for audit and reconciliation. All pending auths reconcile to a terminal state via the existing reconcile primitive. Inbound clearings received after close follow the standard force-post / late-presentment path — Lightspark absorbs the loss if a post-hoc pull on the now-unbound source fails. Funding-source bindings are detached. Refunds already in flight still complete because Lightspark holds the card-reserve keys. - - `fundingSources` change: replaces the ordered binding. No webhook fires for this; read the card back to confirm the new list. The `card.state_change` webhook fires on every successful `state` transition. operationId: updateCardById diff --git a/openapi/paths/cards/cards_{id}.yaml b/openapi/paths/cards/cards_{id}.yaml index fbbcb086b..91eb038a0 100644 --- a/openapi/paths/cards/cards_{id}.yaml +++ b/openapi/paths/cards/cards_{id}.yaml @@ -119,9 +119,6 @@ patch: bindings are detached. Refunds already in flight still complete because Lightspark holds the card-reserve keys. - - `fundingSources` change: replaces the ordered binding. No webhook - fires for this; read the card back to confirm the new list. - The `card.state_change` webhook fires on every successful `state` transition.