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..a876f9a54 100644 --- a/mintlify/openapi.yaml +++ b/mintlify/openapi.yaml @@ -8771,9 +8771,8 @@ 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. - 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 +11751,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 +27174,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 +27406,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..eac705db7 100644 --- a/mintlify/snippets/cards/funding-sources.mdx +++ b/mintlify/snippets/cards/funding-sources.mdx @@ -55,9 +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. -`CARD.FUNDING_SOURCE_CHANGE` fires on every successful update with the -post-change `Card` resource. - ### Errors | Status | Code | What it means | diff --git a/mintlify/snippets/cards/implementation-overview.mdx b/mintlify/snippets/cards/implementation-overview.mdx index 84c52b9a8..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 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 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/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..9e51984c7 100644 --- a/mintlify/snippets/cards/webhooks.mdx +++ b/mintlify/snippets/cards/webhooks.mdx @@ -1,21 +1,23 @@ -Cards add two webhook event types 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.FUNDING_SOURCE_CHANGE` | Whenever `PATCH /cards/{id}` updates the `fundingSources` array. | +| `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. | -All three carry the standard envelope: +All of them carry the standard envelope: ```json { @@ -73,20 +75,13 @@ 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 -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. diff --git a/openapi.yaml b/openapi.yaml index d04857649..a876f9a54 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -8771,9 +8771,8 @@ 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. - 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 +11751,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 +27174,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 +27406,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..91eb038a0 100644 --- a/openapi/paths/cards/cards_{id}.yaml +++ b/openapi/paths/cards/cards_{id}.yaml @@ -119,13 +119,9 @@ 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. - 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