From 1b25788cf58f5b72b78964c58205c3d31501d4a7 Mon Sep 17 00:00:00 2001 From: carsonp6 Date: Wed, 5 Aug 2026 14:32:13 -0700 Subject: [PATCH 1/5] feat(webhooks): add wallet-operation partner webhook Document the wallet-operation webhook that fires when an asynchronous embedded-wallet operation reaches a terminal state: WALLET_OPERATION.COMPLETED on terminal success, WALLET_OPERATION.FAILED on terminal failure. The specific op is carried in data.operationType (auth_credential.delete, session.revoke, wallet.export); data.status is lowercase completed/failed. Adds WalletOperationWebhook / WalletOperationWebhookData / OperationError schemas, the two WALLET_OPERATION.* WebhookType enum values, and registers the webhook in the root spec. Additive only; no API version bump. A data-returning result (wallet.export) is retrieved by resubmitting the original signed request until it returns the result -- never delivered in the webhook. Co-Authored-By: Claude Opus 4.8 --- mintlify/openapi.yaml | 131 ++++++++++++++++++ openapi.yaml | 131 ++++++++++++++++++ .../schemas/webhooks/OperationError.yaml | 8 ++ .../webhooks/WalletOperationWebhook.yaml | 13 ++ .../webhooks/WalletOperationWebhookData.yaml | 30 ++++ .../schemas/webhooks/WebhookType.yaml | 2 + openapi/openapi.yaml | 2 + openapi/webhooks/wallet-operation.yaml | 94 +++++++++++++ 8 files changed, 411 insertions(+) create mode 100644 openapi/components/schemas/webhooks/OperationError.yaml create mode 100644 openapi/components/schemas/webhooks/WalletOperationWebhook.yaml create mode 100644 openapi/components/schemas/webhooks/WalletOperationWebhookData.yaml create mode 100644 openapi/webhooks/wallet-operation.yaml diff --git a/mintlify/openapi.yaml b/mintlify/openapi.yaml index 4d299878d..20140ebcb 100644 --- a/mintlify/openapi.yaml +++ b/mintlify/openapi.yaml @@ -11260,6 +11260,81 @@ webhooks: application/json: schema: $ref: '#/components/schemas/Error409' + wallet-operation: + post: + summary: Wallet operation completed or failed + description: | + Webhook that is called when an asynchronous embedded-wallet operation reaches a terminal state. Fires `WALLET_OPERATION.COMPLETED` on terminal success and `WALLET_OPERATION.FAILED` on terminal failure. + + The specific operation is carried in `data.operationType` (`auth_credential.delete`, `session.revoke`, or `wallet.export`). The webhook carries no sensitive result material — only the `operationId`, `operationType`, `status`, and (on failure) `error.code`. For a data-returning operation (`wallet.export`), the result is never delivered in the webhook; retrieve it by resubmitting the original signed export request until it returns the result. + + 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: walletOperationWebhook + tags: + - Webhooks + security: + - WebhookSignature: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/WalletOperationWebhook' + examples: + completed: + summary: Wallet export completed + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000040 + type: WALLET_OPERATION.COMPLETED + timestamp: '2026-06-08T14:31:00Z' + data: + operationId: Operation:019542f5-b3e7-1d02-0000-000000000099 + operationType: wallet.export + status: completed + failed: + summary: Session revoke failed terminally + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000041 + type: WALLET_OPERATION.FAILED + timestamp: '2026-06-08T14:32:00Z' + data: + operationId: Operation:019542f5-b3e7-1d02-0000-00000000009a + operationType: session.revoke + status: failed + error: + code: DeleteApiKeysFailed + 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' components: securitySchemes: BasicAuth: @@ -24675,6 +24750,8 @@ components: - CARD_TRANSACTION.SETTLED - CARD_TRANSACTION.REFUNDED - CARD_TRANSACTION.EXCEPTION + - WALLET_OPERATION.COMPLETED + - WALLET_OPERATION.FAILED - TEST description: Type of webhook event in OBJECT.EVENT dot-notation. The part before the dot identifies the resource, the part after identifies the event. This lets consumers route purely on type without inspecting data.status. BaseWebhook: @@ -24916,6 +24993,60 @@ components: - CARD_TRANSACTION.SETTLED - CARD_TRANSACTION.REFUNDED - CARD_TRANSACTION.EXCEPTION + OperationError: + type: object + required: + - code + properties: + code: + type: string + description: Machine-readable failure code for a `FAILED` operation. + example: DeleteApiKeysFailed + WalletOperationWebhookData: + type: object + required: + - operationId + - operationType + - status + properties: + operationId: + type: string + description: The `Operation:` id of the operation that reached a terminal state. + example: Operation:019542f5-b3e7-1d02-0000-000000000099 + operationType: + type: string + description: The kind of operation that reached a terminal state. + enum: + - auth_credential.delete + - session.revoke + - wallet.export + example: wallet.export + status: + type: string + description: Terminal status of the operation. + enum: + - completed + - failed + example: completed + error: + anyOf: + - $ref: '#/components/schemas/OperationError' + - type: 'null' + description: Present only on `failed`; `null` otherwise. + WalletOperationWebhook: + allOf: + - $ref: '#/components/schemas/BaseWebhook' + - type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/WalletOperationWebhookData' + type: + type: string + enum: + - WALLET_OPERATION.COMPLETED + - WALLET_OPERATION.FAILED requestBodies: DocumentUploadRequestBody: required: true diff --git a/openapi.yaml b/openapi.yaml index 4d299878d..20140ebcb 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -11260,6 +11260,81 @@ webhooks: application/json: schema: $ref: '#/components/schemas/Error409' + wallet-operation: + post: + summary: Wallet operation completed or failed + description: | + Webhook that is called when an asynchronous embedded-wallet operation reaches a terminal state. Fires `WALLET_OPERATION.COMPLETED` on terminal success and `WALLET_OPERATION.FAILED` on terminal failure. + + The specific operation is carried in `data.operationType` (`auth_credential.delete`, `session.revoke`, or `wallet.export`). The webhook carries no sensitive result material — only the `operationId`, `operationType`, `status`, and (on failure) `error.code`. For a data-returning operation (`wallet.export`), the result is never delivered in the webhook; retrieve it by resubmitting the original signed export request until it returns the result. + + 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: walletOperationWebhook + tags: + - Webhooks + security: + - WebhookSignature: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/WalletOperationWebhook' + examples: + completed: + summary: Wallet export completed + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000040 + type: WALLET_OPERATION.COMPLETED + timestamp: '2026-06-08T14:31:00Z' + data: + operationId: Operation:019542f5-b3e7-1d02-0000-000000000099 + operationType: wallet.export + status: completed + failed: + summary: Session revoke failed terminally + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000041 + type: WALLET_OPERATION.FAILED + timestamp: '2026-06-08T14:32:00Z' + data: + operationId: Operation:019542f5-b3e7-1d02-0000-00000000009a + operationType: session.revoke + status: failed + error: + code: DeleteApiKeysFailed + 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' components: securitySchemes: BasicAuth: @@ -24675,6 +24750,8 @@ components: - CARD_TRANSACTION.SETTLED - CARD_TRANSACTION.REFUNDED - CARD_TRANSACTION.EXCEPTION + - WALLET_OPERATION.COMPLETED + - WALLET_OPERATION.FAILED - TEST description: Type of webhook event in OBJECT.EVENT dot-notation. The part before the dot identifies the resource, the part after identifies the event. This lets consumers route purely on type without inspecting data.status. BaseWebhook: @@ -24916,6 +24993,60 @@ components: - CARD_TRANSACTION.SETTLED - CARD_TRANSACTION.REFUNDED - CARD_TRANSACTION.EXCEPTION + OperationError: + type: object + required: + - code + properties: + code: + type: string + description: Machine-readable failure code for a `FAILED` operation. + example: DeleteApiKeysFailed + WalletOperationWebhookData: + type: object + required: + - operationId + - operationType + - status + properties: + operationId: + type: string + description: The `Operation:` id of the operation that reached a terminal state. + example: Operation:019542f5-b3e7-1d02-0000-000000000099 + operationType: + type: string + description: The kind of operation that reached a terminal state. + enum: + - auth_credential.delete + - session.revoke + - wallet.export + example: wallet.export + status: + type: string + description: Terminal status of the operation. + enum: + - completed + - failed + example: completed + error: + anyOf: + - $ref: '#/components/schemas/OperationError' + - type: 'null' + description: Present only on `failed`; `null` otherwise. + WalletOperationWebhook: + allOf: + - $ref: '#/components/schemas/BaseWebhook' + - type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/WalletOperationWebhookData' + type: + type: string + enum: + - WALLET_OPERATION.COMPLETED + - WALLET_OPERATION.FAILED requestBodies: DocumentUploadRequestBody: required: true diff --git a/openapi/components/schemas/webhooks/OperationError.yaml b/openapi/components/schemas/webhooks/OperationError.yaml new file mode 100644 index 000000000..7a4533345 --- /dev/null +++ b/openapi/components/schemas/webhooks/OperationError.yaml @@ -0,0 +1,8 @@ +type: object +required: + - code +properties: + code: + type: string + description: Machine-readable failure code for a `FAILED` operation. + example: DeleteApiKeysFailed diff --git a/openapi/components/schemas/webhooks/WalletOperationWebhook.yaml b/openapi/components/schemas/webhooks/WalletOperationWebhook.yaml new file mode 100644 index 000000000..a7dd45d8f --- /dev/null +++ b/openapi/components/schemas/webhooks/WalletOperationWebhook.yaml @@ -0,0 +1,13 @@ +allOf: + - $ref: ./BaseWebhook.yaml + - type: object + required: + - data + properties: + data: + $ref: ./WalletOperationWebhookData.yaml + type: + type: string + enum: + - WALLET_OPERATION.COMPLETED + - WALLET_OPERATION.FAILED diff --git a/openapi/components/schemas/webhooks/WalletOperationWebhookData.yaml b/openapi/components/schemas/webhooks/WalletOperationWebhookData.yaml new file mode 100644 index 000000000..b90638e6f --- /dev/null +++ b/openapi/components/schemas/webhooks/WalletOperationWebhookData.yaml @@ -0,0 +1,30 @@ +type: object +required: + - operationId + - operationType + - status +properties: + operationId: + type: string + description: The `Operation:` id of the operation that reached a terminal state. + example: Operation:019542f5-b3e7-1d02-0000-000000000099 + operationType: + type: string + description: The kind of operation that reached a terminal state. + enum: + - auth_credential.delete + - session.revoke + - wallet.export + example: wallet.export + status: + type: string + description: Terminal status of the operation. + enum: + - completed + - failed + example: completed + error: + anyOf: + - $ref: ./OperationError.yaml + - type: 'null' + description: Present only on `failed`; `null` otherwise. diff --git a/openapi/components/schemas/webhooks/WebhookType.yaml b/openapi/components/schemas/webhooks/WebhookType.yaml index fab72c524..e78db694d 100644 --- a/openapi/components/schemas/webhooks/WebhookType.yaml +++ b/openapi/components/schemas/webhooks/WebhookType.yaml @@ -39,6 +39,8 @@ enum: - CARD_TRANSACTION.SETTLED - CARD_TRANSACTION.REFUNDED - CARD_TRANSACTION.EXCEPTION + - WALLET_OPERATION.COMPLETED + - WALLET_OPERATION.FAILED - TEST description: >- Type of webhook event in OBJECT.EVENT dot-notation. The part before the dot diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index b6143351f..9b0949716 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -397,6 +397,8 @@ webhooks: $ref: webhooks/card-funding-source-change.yaml card-transaction: $ref: webhooks/card-transaction.yaml + wallet-operation: + $ref: webhooks/wallet-operation.yaml security: - BasicAuth: [] - AgentAuth: [] diff --git a/openapi/webhooks/wallet-operation.yaml b/openapi/webhooks/wallet-operation.yaml new file mode 100644 index 000000000..720f8bff3 --- /dev/null +++ b/openapi/webhooks/wallet-operation.yaml @@ -0,0 +1,94 @@ +post: + summary: Wallet operation completed or failed + description: > + Webhook that is called when an asynchronous embedded-wallet operation + reaches a terminal state. Fires `WALLET_OPERATION.COMPLETED` on terminal + success and `WALLET_OPERATION.FAILED` on terminal failure. + + + The specific operation is carried in `data.operationType` + (`auth_credential.delete`, `session.revoke`, or `wallet.export`). The webhook + carries no sensitive result material — only the `operationId`, + `operationType`, `status`, and (on failure) `error.code`. For a + data-returning operation (`wallet.export`), the result is never delivered in + the webhook; retrieve it by resubmitting the original signed export request + until it returns the result. + + + 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: walletOperationWebhook + tags: + - Webhooks + security: + - WebhookSignature: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '../components/schemas/webhooks/WalletOperationWebhook.yaml' + examples: + completed: + summary: Wallet export completed + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000040 + type: WALLET_OPERATION.COMPLETED + timestamp: '2026-06-08T14:31:00Z' + data: + operationId: Operation:019542f5-b3e7-1d02-0000-000000000099 + operationType: wallet.export + status: completed + failed: + summary: Session revoke failed terminally + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000041 + type: WALLET_OPERATION.FAILED + timestamp: '2026-06-08T14:32:00Z' + data: + operationId: Operation:019542f5-b3e7-1d02-0000-00000000009a + operationType: session.revoke + status: failed + error: + code: DeleteApiKeysFailed + 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 3647ddc6e94640d5f92e03b8d79200657b135b07 Mon Sep 17 00:00:00 2001 From: carsonp6 Date: Fri, 21 Aug 2026 13:31:54 -0700 Subject: [PATCH 2/5] fix(grid-api): enforce completed/failed terminal-state invariant on wallet-operation webhook Splits WalletOperationWebhookData into a status-discriminated oneOf so the schema matches what sparkcore actually guarantees: a `failed` event always carries `error.code` (every FAILED_TERMINAL transition sets last_error_code) and a `completed` event never carries `error` at all. --- mintlify/openapi.yaml | 51 ++++++++++++++++--- openapi.yaml | 51 ++++++++++++++++--- .../schemas/webhooks/OperationError.yaml | 1 + .../WalletOperationCompletedData.yaml | 25 +++++++++ .../webhooks/WalletOperationFailedData.yaml | 28 ++++++++++ .../webhooks/WalletOperationWebhookData.yaml | 39 ++++---------- 6 files changed, 149 insertions(+), 46 deletions(-) create mode 100644 openapi/components/schemas/webhooks/WalletOperationCompletedData.yaml create mode 100644 openapi/components/schemas/webhooks/WalletOperationFailedData.yaml diff --git a/mintlify/openapi.yaml b/mintlify/openapi.yaml index bbdfa7020..1085ac1af 100644 --- a/mintlify/openapi.yaml +++ b/mintlify/openapi.yaml @@ -25849,8 +25849,35 @@ components: - CARD_TRANSACTION.SETTLED - CARD_TRANSACTION.REFUNDED - CARD_TRANSACTION.EXCEPTION + WalletOperationCompletedData: + title: Wallet Operation Completed Data + type: object + required: + - operationId + - operationType + - status + properties: + operationId: + type: string + description: The `Operation:` id of the operation that reached a terminal state. + example: Operation:019542f5-b3e7-1d02-0000-000000000099 + operationType: + type: string + description: The kind of operation that reached a terminal state. + enum: + - auth_credential.delete + - session.revoke + - wallet.export + example: wallet.export + status: + type: string + enum: + - completed + description: Terminal status of the operation. + example: completed OperationError: type: object + description: Failure details for a terminally failed operation. required: - code properties: @@ -25858,12 +25885,14 @@ components: type: string description: Machine-readable failure code for a `FAILED` operation. example: DeleteApiKeysFailed - WalletOperationWebhookData: + WalletOperationFailedData: + title: Wallet Operation Failed Data type: object required: - operationId - operationType - status + - error properties: operationId: type: string @@ -25879,16 +25908,22 @@ components: example: wallet.export status: type: string - description: Terminal status of the operation. enum: - - completed - failed - example: completed + description: Terminal status of the operation. + example: failed error: - anyOf: - - $ref: '#/components/schemas/OperationError' - - type: 'null' - description: Present only on `failed`; `null` otherwise. + $ref: '#/components/schemas/OperationError' + WalletOperationWebhookData: + title: Wallet Operation Data + oneOf: + - $ref: '#/components/schemas/WalletOperationCompletedData' + - $ref: '#/components/schemas/WalletOperationFailedData' + discriminator: + propertyName: status + mapping: + completed: '#/components/schemas/WalletOperationCompletedData' + failed: '#/components/schemas/WalletOperationFailedData' WalletOperationWebhook: allOf: - $ref: '#/components/schemas/BaseWebhook' diff --git a/openapi.yaml b/openapi.yaml index bbdfa7020..1085ac1af 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -25849,8 +25849,35 @@ components: - CARD_TRANSACTION.SETTLED - CARD_TRANSACTION.REFUNDED - CARD_TRANSACTION.EXCEPTION + WalletOperationCompletedData: + title: Wallet Operation Completed Data + type: object + required: + - operationId + - operationType + - status + properties: + operationId: + type: string + description: The `Operation:` id of the operation that reached a terminal state. + example: Operation:019542f5-b3e7-1d02-0000-000000000099 + operationType: + type: string + description: The kind of operation that reached a terminal state. + enum: + - auth_credential.delete + - session.revoke + - wallet.export + example: wallet.export + status: + type: string + enum: + - completed + description: Terminal status of the operation. + example: completed OperationError: type: object + description: Failure details for a terminally failed operation. required: - code properties: @@ -25858,12 +25885,14 @@ components: type: string description: Machine-readable failure code for a `FAILED` operation. example: DeleteApiKeysFailed - WalletOperationWebhookData: + WalletOperationFailedData: + title: Wallet Operation Failed Data type: object required: - operationId - operationType - status + - error properties: operationId: type: string @@ -25879,16 +25908,22 @@ components: example: wallet.export status: type: string - description: Terminal status of the operation. enum: - - completed - failed - example: completed + description: Terminal status of the operation. + example: failed error: - anyOf: - - $ref: '#/components/schemas/OperationError' - - type: 'null' - description: Present only on `failed`; `null` otherwise. + $ref: '#/components/schemas/OperationError' + WalletOperationWebhookData: + title: Wallet Operation Data + oneOf: + - $ref: '#/components/schemas/WalletOperationCompletedData' + - $ref: '#/components/schemas/WalletOperationFailedData' + discriminator: + propertyName: status + mapping: + completed: '#/components/schemas/WalletOperationCompletedData' + failed: '#/components/schemas/WalletOperationFailedData' WalletOperationWebhook: allOf: - $ref: '#/components/schemas/BaseWebhook' diff --git a/openapi/components/schemas/webhooks/OperationError.yaml b/openapi/components/schemas/webhooks/OperationError.yaml index 7a4533345..263f99696 100644 --- a/openapi/components/schemas/webhooks/OperationError.yaml +++ b/openapi/components/schemas/webhooks/OperationError.yaml @@ -1,4 +1,5 @@ type: object +description: Failure details for a terminally failed operation. required: - code properties: diff --git a/openapi/components/schemas/webhooks/WalletOperationCompletedData.yaml b/openapi/components/schemas/webhooks/WalletOperationCompletedData.yaml new file mode 100644 index 000000000..1bbf20c22 --- /dev/null +++ b/openapi/components/schemas/webhooks/WalletOperationCompletedData.yaml @@ -0,0 +1,25 @@ +title: Wallet Operation Completed Data +type: object +required: + - operationId + - operationType + - status +properties: + operationId: + type: string + description: The `Operation:` id of the operation that reached a terminal state. + example: Operation:019542f5-b3e7-1d02-0000-000000000099 + operationType: + type: string + description: The kind of operation that reached a terminal state. + enum: + - auth_credential.delete + - session.revoke + - wallet.export + example: wallet.export + status: + type: string + enum: + - completed + description: Terminal status of the operation. + example: completed diff --git a/openapi/components/schemas/webhooks/WalletOperationFailedData.yaml b/openapi/components/schemas/webhooks/WalletOperationFailedData.yaml new file mode 100644 index 000000000..826bc6137 --- /dev/null +++ b/openapi/components/schemas/webhooks/WalletOperationFailedData.yaml @@ -0,0 +1,28 @@ +title: Wallet Operation Failed Data +type: object +required: + - operationId + - operationType + - status + - error +properties: + operationId: + type: string + description: The `Operation:` id of the operation that reached a terminal state. + example: Operation:019542f5-b3e7-1d02-0000-000000000099 + operationType: + type: string + description: The kind of operation that reached a terminal state. + enum: + - auth_credential.delete + - session.revoke + - wallet.export + example: wallet.export + status: + type: string + enum: + - failed + description: Terminal status of the operation. + example: failed + error: + $ref: ./OperationError.yaml diff --git a/openapi/components/schemas/webhooks/WalletOperationWebhookData.yaml b/openapi/components/schemas/webhooks/WalletOperationWebhookData.yaml index b90638e6f..2094719be 100644 --- a/openapi/components/schemas/webhooks/WalletOperationWebhookData.yaml +++ b/openapi/components/schemas/webhooks/WalletOperationWebhookData.yaml @@ -1,30 +1,9 @@ -type: object -required: - - operationId - - operationType - - status -properties: - operationId: - type: string - description: The `Operation:` id of the operation that reached a terminal state. - example: Operation:019542f5-b3e7-1d02-0000-000000000099 - operationType: - type: string - description: The kind of operation that reached a terminal state. - enum: - - auth_credential.delete - - session.revoke - - wallet.export - example: wallet.export - status: - type: string - description: Terminal status of the operation. - enum: - - completed - - failed - example: completed - error: - anyOf: - - $ref: ./OperationError.yaml - - type: 'null' - description: Present only on `failed`; `null` otherwise. +title: Wallet Operation Data +oneOf: + - $ref: ./WalletOperationCompletedData.yaml + - $ref: ./WalletOperationFailedData.yaml +discriminator: + propertyName: status + mapping: + completed: ./WalletOperationCompletedData.yaml + failed: ./WalletOperationFailedData.yaml From dc96b2540dafb8989580cfc80098157d29f3f895 Mon Sep 17 00:00:00 2001 From: carsonp6 Date: Fri, 21 Aug 2026 13:50:42 -0700 Subject: [PATCH 3/5] feat(grid-api): make the WALLET_OPERATION webhook self-contained and correlatable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds requestId (the integrator's own Request-Id from the signed retry that produced the terminal result) as the primary correlation key, and resourceType/resourceId (the affected AuthMethod/Session/InternalAccount) so the webhook can be handled without a follow-up API call. Repositions operationId as a Grid-internal support reference, not a correlator. Documents the correlation model (requestId to match your request, envelope id to dedupe redeliveries, operationId for support) and adds the missing WALLET_OPERATION.* row to the webhook retry-policy table. All new fields are sourced from data EntGridTurnkeyActivity already persists (pending_request_id, correlation_key, internal_account_id) — no new sparkcore persistence required. Wiring them into the actual webhook payload is a sparkcore emitter change tracked separately, not part of this spec-only PR. --- mintlify/openapi.yaml | 70 +++++++++++++++++-- mintlify/snippets/webhooks.mdx | 1 + openapi.yaml | 70 +++++++++++++++++-- .../WalletOperationCompletedData.yaml | 36 +++++++++- .../webhooks/WalletOperationFailedData.yaml | 36 +++++++++- openapi/webhooks/wallet-operation.yaml | 46 ++++++------ 6 files changed, 222 insertions(+), 37 deletions(-) diff --git a/mintlify/openapi.yaml b/mintlify/openapi.yaml index 1085ac1af..664dd6071 100644 --- a/mintlify/openapi.yaml +++ b/mintlify/openapi.yaml @@ -11774,16 +11774,30 @@ webhooks: post: summary: Wallet operation completed or failed description: | - Webhook that is called when an asynchronous embedded-wallet operation reaches a terminal state. Fires `WALLET_OPERATION.COMPLETED` on terminal success and `WALLET_OPERATION.FAILED` on terminal failure. + Webhook that is called when an asynchronous embedded-wallet operation reaches a terminal state. Fires `WALLET_OPERATION.COMPLETED` on terminal success and `WALLET_OPERATION.FAILED` on terminal failure. The payload is self-contained — handle it from the fields below with no follow-up API call needed. - The specific operation is carried in `data.operationType` (`auth_credential.delete`, `session.revoke`, or `wallet.export`). The webhook carries no sensitive result material — only the `operationId`, `operationType`, `status`, and (on failure) `error.code`. For a data-returning operation (`wallet.export`), the result is never delivered in the webhook; retrieve it by resubmitting the original signed export request until it returns the result. + The specific operation is carried in `data.operationType` (`auth_credential.delete`, `session.revoke`, or `wallet.export`), and the business resource it affected is carried in `data.resourceType` / `data.resourceId`: + + | `operationType` | `resourceType` | `resourceId` identifies | + | --- | --- | --- | + | `auth_credential.delete` | `AUTH_METHOD` | the deleted credential | + | `session.revoke` | `SESSION` | the revoked session | + | `wallet.export` | `INTERNAL_ACCOUNT` | the account whose wallet was exported | + + The webhook carries no sensitive result material — for `wallet.export`, the export bundle itself is never delivered here; retrieve it by resubmitting the original signed export request until it returns the result. + + ### Correlating this webhook + + - **`data.requestId`** is the primary correlation key. It is the same `Request-Id` value you supplied on the signed retry that produced this terminal result — echo it against the `Request-Id` you sent (and, if you polled through one or more `200 { status: "PROCESSING" }` responses, against the value you kept resending). + - **`id`** (the top-level webhook envelope id) is for deduplication. Grid may redeliver a webhook after a transient failure; track `id` to avoid double-processing. + - **`data.operationId`** is a Grid-internal identifier for the operation. It's useful when contacting support about a specific operation, but isn't intended as a correlation key. 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: + 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 @@ -11810,7 +11824,10 @@ webhooks: timestamp: '2026-06-08T14:31:00Z' data: operationId: Operation:019542f5-b3e7-1d02-0000-000000000099 + requestId: Request:9f7a2c10-5e88-4fb1-bd0e-1c3a8e7b2d45 operationType: wallet.export + resourceType: INTERNAL_ACCOUNT + resourceId: InternalAccount:019542f5-b3e7-1d02-0000-000000000005 status: completed failed: summary: Session revoke failed terminally @@ -11820,7 +11837,10 @@ webhooks: timestamp: '2026-06-08T14:32:00Z' data: operationId: Operation:019542f5-b3e7-1d02-0000-00000000009a + requestId: Request:3c1e5a2f-8b44-4d7a-9e10-6f2b8c4d1a90 operationType: session.revoke + resourceType: SESSION + resourceId: Session:019542f5-b3e7-1d02-0000-00000000009a status: failed error: code: DeleteApiKeysFailed @@ -25854,13 +25874,20 @@ components: type: object required: - operationId + - requestId - operationType + - resourceType + - resourceId - status properties: operationId: type: string - description: The `Operation:` id of the operation that reached a terminal state. + description: Grid-internal identifier for this operation. Useful when contacting support about a specific operation; not a correlation key — use `requestId` to match this webhook to the request you made. example: Operation:019542f5-b3e7-1d02-0000-000000000099 + requestId: + type: string + description: 'The `Request-Id` you supplied on the signed retry that produced this terminal result — the same value you would have echoed on every retry had you received a `200 { status: "PROCESSING" }` response while it was settling. This is the primary way to correlate this webhook to the request you made.' + example: Request:9f7a2c10-5e88-4fb1-bd0e-1c3a8e7b2d45 operationType: type: string description: The kind of operation that reached a terminal state. @@ -25869,6 +25896,18 @@ components: - session.revoke - wallet.export example: wallet.export + resourceType: + type: string + description: 'The kind of business resource `resourceId` identifies. Determined by `operationType`: `auth_credential.delete` → `AUTH_METHOD`, `session.revoke` → `SESSION`, `wallet.export` → `INTERNAL_ACCOUNT`.' + enum: + - AUTH_METHOD + - SESSION + - INTERNAL_ACCOUNT + example: INTERNAL_ACCOUNT + resourceId: + type: string + description: 'LSID of the business resource this operation affected: the deleted `AuthMethod:` for `auth_credential.delete`, the revoked `Session:` for `session.revoke`, or the `InternalAccount:` whose wallet was exported for `wallet.export`.' + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000005 status: type: string enum: @@ -25890,14 +25929,21 @@ components: type: object required: - operationId + - requestId - operationType + - resourceType + - resourceId - status - error properties: operationId: type: string - description: The `Operation:` id of the operation that reached a terminal state. + description: Grid-internal identifier for this operation. Useful when contacting support about a specific operation; not a correlation key — use `requestId` to match this webhook to the request you made. example: Operation:019542f5-b3e7-1d02-0000-000000000099 + requestId: + type: string + description: 'The `Request-Id` you supplied on the signed retry that produced this terminal result — the same value you would have echoed on every retry had you received a `200 { status: "PROCESSING" }` response while it was settling. This is the primary way to correlate this webhook to the request you made.' + example: Request:9f7a2c10-5e88-4fb1-bd0e-1c3a8e7b2d45 operationType: type: string description: The kind of operation that reached a terminal state. @@ -25906,6 +25952,18 @@ components: - session.revoke - wallet.export example: wallet.export + resourceType: + type: string + description: 'The kind of business resource `resourceId` identifies. Determined by `operationType`: `auth_credential.delete` → `AUTH_METHOD`, `session.revoke` → `SESSION`, `wallet.export` → `INTERNAL_ACCOUNT`.' + enum: + - AUTH_METHOD + - SESSION + - INTERNAL_ACCOUNT + example: SESSION + resourceId: + type: string + description: 'LSID of the business resource this operation affected: the deleted `AuthMethod:` for `auth_credential.delete`, the revoked `Session:` for `session.revoke`, or the `InternalAccount:` whose wallet was exported for `wallet.export`.' + example: Session:019542f5-b3e7-1d02-0000-00000000009a status: type: string enum: diff --git a/mintlify/snippets/webhooks.mdx b/mintlify/snippets/webhooks.mdx index e1c2abfe1..ca0a1c52a 100644 --- a/mintlify/snippets/webhooks.mdx +++ b/mintlify/snippets/webhooks.mdx @@ -200,6 +200,7 @@ The Grid API will retry webhooks with the following policy based on the webhook | `OUTGOING_PAYMENT.*` | Retry with exponential backoff up to 7 days with maximum interval of 30 mins | No retry on 409 (duplicate webhooks) | | `INCOMING_PAYMENT.*` | Retry with exponential backoff up to 7 days with maximum interval of 30 mins | No retry on: 409 (duplicate webhook) or PENDING status since it is served as an approval mechanism in-flow | | `BULK_UPLOAD.*` | Retry with exponential backoff up to 7 days with maximum interval of 30 mins | No retry on 409 (duplicate webhooks) | +| `WALLET_OPERATION.*` | Retry with exponential backoff up to 7 days with maximum interval of 30 mins | No retry on 409 (duplicate webhooks) | | `INVITATION.*` | Retry with exponential backoff up to 7 days with maximum interval of 30 mins | No retry on 409 (duplicate webhooks) | | `CUSTOMER.*` | Retry with exponential backoff up to 7 days with maximum interval of 30 mins | No retry on 409 (duplicate webhooks) | | `ACCOUNT.*` | Retry with exponential backoff up to 7 days with maximum interval of 30 mins | No retry on 409 (duplicate webhooks) | diff --git a/openapi.yaml b/openapi.yaml index 1085ac1af..664dd6071 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -11774,16 +11774,30 @@ webhooks: post: summary: Wallet operation completed or failed description: | - Webhook that is called when an asynchronous embedded-wallet operation reaches a terminal state. Fires `WALLET_OPERATION.COMPLETED` on terminal success and `WALLET_OPERATION.FAILED` on terminal failure. + Webhook that is called when an asynchronous embedded-wallet operation reaches a terminal state. Fires `WALLET_OPERATION.COMPLETED` on terminal success and `WALLET_OPERATION.FAILED` on terminal failure. The payload is self-contained — handle it from the fields below with no follow-up API call needed. - The specific operation is carried in `data.operationType` (`auth_credential.delete`, `session.revoke`, or `wallet.export`). The webhook carries no sensitive result material — only the `operationId`, `operationType`, `status`, and (on failure) `error.code`. For a data-returning operation (`wallet.export`), the result is never delivered in the webhook; retrieve it by resubmitting the original signed export request until it returns the result. + The specific operation is carried in `data.operationType` (`auth_credential.delete`, `session.revoke`, or `wallet.export`), and the business resource it affected is carried in `data.resourceType` / `data.resourceId`: + + | `operationType` | `resourceType` | `resourceId` identifies | + | --- | --- | --- | + | `auth_credential.delete` | `AUTH_METHOD` | the deleted credential | + | `session.revoke` | `SESSION` | the revoked session | + | `wallet.export` | `INTERNAL_ACCOUNT` | the account whose wallet was exported | + + The webhook carries no sensitive result material — for `wallet.export`, the export bundle itself is never delivered here; retrieve it by resubmitting the original signed export request until it returns the result. + + ### Correlating this webhook + + - **`data.requestId`** is the primary correlation key. It is the same `Request-Id` value you supplied on the signed retry that produced this terminal result — echo it against the `Request-Id` you sent (and, if you polled through one or more `200 { status: "PROCESSING" }` responses, against the value you kept resending). + - **`id`** (the top-level webhook envelope id) is for deduplication. Grid may redeliver a webhook after a transient failure; track `id` to avoid double-processing. + - **`data.operationId`** is a Grid-internal identifier for the operation. It's useful when contacting support about a specific operation, but isn't intended as a correlation key. 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: + 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 @@ -11810,7 +11824,10 @@ webhooks: timestamp: '2026-06-08T14:31:00Z' data: operationId: Operation:019542f5-b3e7-1d02-0000-000000000099 + requestId: Request:9f7a2c10-5e88-4fb1-bd0e-1c3a8e7b2d45 operationType: wallet.export + resourceType: INTERNAL_ACCOUNT + resourceId: InternalAccount:019542f5-b3e7-1d02-0000-000000000005 status: completed failed: summary: Session revoke failed terminally @@ -11820,7 +11837,10 @@ webhooks: timestamp: '2026-06-08T14:32:00Z' data: operationId: Operation:019542f5-b3e7-1d02-0000-00000000009a + requestId: Request:3c1e5a2f-8b44-4d7a-9e10-6f2b8c4d1a90 operationType: session.revoke + resourceType: SESSION + resourceId: Session:019542f5-b3e7-1d02-0000-00000000009a status: failed error: code: DeleteApiKeysFailed @@ -25854,13 +25874,20 @@ components: type: object required: - operationId + - requestId - operationType + - resourceType + - resourceId - status properties: operationId: type: string - description: The `Operation:` id of the operation that reached a terminal state. + description: Grid-internal identifier for this operation. Useful when contacting support about a specific operation; not a correlation key — use `requestId` to match this webhook to the request you made. example: Operation:019542f5-b3e7-1d02-0000-000000000099 + requestId: + type: string + description: 'The `Request-Id` you supplied on the signed retry that produced this terminal result — the same value you would have echoed on every retry had you received a `200 { status: "PROCESSING" }` response while it was settling. This is the primary way to correlate this webhook to the request you made.' + example: Request:9f7a2c10-5e88-4fb1-bd0e-1c3a8e7b2d45 operationType: type: string description: The kind of operation that reached a terminal state. @@ -25869,6 +25896,18 @@ components: - session.revoke - wallet.export example: wallet.export + resourceType: + type: string + description: 'The kind of business resource `resourceId` identifies. Determined by `operationType`: `auth_credential.delete` → `AUTH_METHOD`, `session.revoke` → `SESSION`, `wallet.export` → `INTERNAL_ACCOUNT`.' + enum: + - AUTH_METHOD + - SESSION + - INTERNAL_ACCOUNT + example: INTERNAL_ACCOUNT + resourceId: + type: string + description: 'LSID of the business resource this operation affected: the deleted `AuthMethod:` for `auth_credential.delete`, the revoked `Session:` for `session.revoke`, or the `InternalAccount:` whose wallet was exported for `wallet.export`.' + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000005 status: type: string enum: @@ -25890,14 +25929,21 @@ components: type: object required: - operationId + - requestId - operationType + - resourceType + - resourceId - status - error properties: operationId: type: string - description: The `Operation:` id of the operation that reached a terminal state. + description: Grid-internal identifier for this operation. Useful when contacting support about a specific operation; not a correlation key — use `requestId` to match this webhook to the request you made. example: Operation:019542f5-b3e7-1d02-0000-000000000099 + requestId: + type: string + description: 'The `Request-Id` you supplied on the signed retry that produced this terminal result — the same value you would have echoed on every retry had you received a `200 { status: "PROCESSING" }` response while it was settling. This is the primary way to correlate this webhook to the request you made.' + example: Request:9f7a2c10-5e88-4fb1-bd0e-1c3a8e7b2d45 operationType: type: string description: The kind of operation that reached a terminal state. @@ -25906,6 +25952,18 @@ components: - session.revoke - wallet.export example: wallet.export + resourceType: + type: string + description: 'The kind of business resource `resourceId` identifies. Determined by `operationType`: `auth_credential.delete` → `AUTH_METHOD`, `session.revoke` → `SESSION`, `wallet.export` → `INTERNAL_ACCOUNT`.' + enum: + - AUTH_METHOD + - SESSION + - INTERNAL_ACCOUNT + example: SESSION + resourceId: + type: string + description: 'LSID of the business resource this operation affected: the deleted `AuthMethod:` for `auth_credential.delete`, the revoked `Session:` for `session.revoke`, or the `InternalAccount:` whose wallet was exported for `wallet.export`.' + example: Session:019542f5-b3e7-1d02-0000-00000000009a status: type: string enum: diff --git a/openapi/components/schemas/webhooks/WalletOperationCompletedData.yaml b/openapi/components/schemas/webhooks/WalletOperationCompletedData.yaml index 1bbf20c22..f0525430c 100644 --- a/openapi/components/schemas/webhooks/WalletOperationCompletedData.yaml +++ b/openapi/components/schemas/webhooks/WalletOperationCompletedData.yaml @@ -2,13 +2,28 @@ title: Wallet Operation Completed Data type: object required: - operationId + - requestId - operationType + - resourceType + - resourceId - status properties: operationId: type: string - description: The `Operation:` id of the operation that reached a terminal state. + description: >- + Grid-internal identifier for this operation. Useful when contacting + support about a specific operation; not a correlation key — use + `requestId` to match this webhook to the request you made. example: Operation:019542f5-b3e7-1d02-0000-000000000099 + requestId: + type: string + description: >- + The `Request-Id` you supplied on the signed retry that produced this + terminal result — the same value you would have echoed on every retry + had you received a `200 { status: "PROCESSING" }` response while it was + settling. This is the primary way to correlate this webhook to the + request you made. + example: Request:9f7a2c10-5e88-4fb1-bd0e-1c3a8e7b2d45 operationType: type: string description: The kind of operation that reached a terminal state. @@ -17,6 +32,25 @@ properties: - session.revoke - wallet.export example: wallet.export + resourceType: + type: string + description: >- + The kind of business resource `resourceId` identifies. Determined by + `operationType`: `auth_credential.delete` → `AUTH_METHOD`, + `session.revoke` → `SESSION`, `wallet.export` → `INTERNAL_ACCOUNT`. + enum: + - AUTH_METHOD + - SESSION + - INTERNAL_ACCOUNT + example: INTERNAL_ACCOUNT + resourceId: + type: string + description: >- + LSID of the business resource this operation affected: the deleted + `AuthMethod:` for `auth_credential.delete`, the revoked + `Session:` for `session.revoke`, or the `InternalAccount:` + whose wallet was exported for `wallet.export`. + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000005 status: type: string enum: diff --git a/openapi/components/schemas/webhooks/WalletOperationFailedData.yaml b/openapi/components/schemas/webhooks/WalletOperationFailedData.yaml index 826bc6137..beb9975b9 100644 --- a/openapi/components/schemas/webhooks/WalletOperationFailedData.yaml +++ b/openapi/components/schemas/webhooks/WalletOperationFailedData.yaml @@ -2,14 +2,29 @@ title: Wallet Operation Failed Data type: object required: - operationId + - requestId - operationType + - resourceType + - resourceId - status - error properties: operationId: type: string - description: The `Operation:` id of the operation that reached a terminal state. + description: >- + Grid-internal identifier for this operation. Useful when contacting + support about a specific operation; not a correlation key — use + `requestId` to match this webhook to the request you made. example: Operation:019542f5-b3e7-1d02-0000-000000000099 + requestId: + type: string + description: >- + The `Request-Id` you supplied on the signed retry that produced this + terminal result — the same value you would have echoed on every retry + had you received a `200 { status: "PROCESSING" }` response while it was + settling. This is the primary way to correlate this webhook to the + request you made. + example: Request:9f7a2c10-5e88-4fb1-bd0e-1c3a8e7b2d45 operationType: type: string description: The kind of operation that reached a terminal state. @@ -18,6 +33,25 @@ properties: - session.revoke - wallet.export example: wallet.export + resourceType: + type: string + description: >- + The kind of business resource `resourceId` identifies. Determined by + `operationType`: `auth_credential.delete` → `AUTH_METHOD`, + `session.revoke` → `SESSION`, `wallet.export` → `INTERNAL_ACCOUNT`. + enum: + - AUTH_METHOD + - SESSION + - INTERNAL_ACCOUNT + example: SESSION + resourceId: + type: string + description: >- + LSID of the business resource this operation affected: the deleted + `AuthMethod:` for `auth_credential.delete`, the revoked + `Session:` for `session.revoke`, or the `InternalAccount:` + whose wallet was exported for `wallet.export`. + example: Session:019542f5-b3e7-1d02-0000-00000000009a status: type: string enum: diff --git a/openapi/webhooks/wallet-operation.yaml b/openapi/webhooks/wallet-operation.yaml index 720f8bff3..12a5a9c33 100644 --- a/openapi/webhooks/wallet-operation.yaml +++ b/openapi/webhooks/wallet-operation.yaml @@ -1,42 +1,36 @@ post: summary: Wallet operation completed or failed - description: > - Webhook that is called when an asynchronous embedded-wallet operation - reaches a terminal state. Fires `WALLET_OPERATION.COMPLETED` on terminal - success and `WALLET_OPERATION.FAILED` on terminal failure. + description: | + Webhook that is called when an asynchronous embedded-wallet operation reaches a terminal state. Fires `WALLET_OPERATION.COMPLETED` on terminal success and `WALLET_OPERATION.FAILED` on terminal failure. The payload is self-contained — handle it from the fields below with no follow-up API call needed. + The specific operation is carried in `data.operationType` (`auth_credential.delete`, `session.revoke`, or `wallet.export`), and the business resource it affected is carried in `data.resourceType` / `data.resourceId`: - The specific operation is carried in `data.operationType` - (`auth_credential.delete`, `session.revoke`, or `wallet.export`). The webhook - carries no sensitive result material — only the `operationId`, - `operationType`, `status`, and (on failure) `error.code`. For a - data-returning operation (`wallet.export`), the result is never delivered in - the webhook; retrieve it by resubmitting the original signed export request - until it returns the result. + | `operationType` | `resourceType` | `resourceId` identifies | + | --- | --- | --- | + | `auth_credential.delete` | `AUTH_METHOD` | the deleted credential | + | `session.revoke` | `SESSION` | the revoked session | + | `wallet.export` | `INTERNAL_ACCOUNT` | the account whose wallet was exported | + The webhook carries no sensitive result material — for `wallet.export`, the export bundle itself is never delivered here; retrieve it by resubmitting the original signed export request until it returns the result. - This endpoint should be implemented by clients of the Grid API. - + ### Correlating this webhook - ### Authentication + - **`data.requestId`** is the primary correlation key. It is the same `Request-Id` value you supplied on the signed retry that produced this terminal result — echo it against the `Request-Id` you sent (and, if you polled through one or more `200 { status: "PROCESSING" }` responses, against the value you kept resending). + - **`id`** (the top-level webhook envelope id) is for deduplication. Grid may redeliver a webhook after a transient failure; track `id` to avoid double-processing. + - **`data.operationId`** is a Grid-internal identifier for the operation. It's useful when contacting support about a specific operation, but isn't intended as a correlation key. + This endpoint should be implemented by clients of the Grid API. - The webhook includes a signature in the `X-Grid-Signature` header that - allows you to verify that the webhook was sent by Grid. + ### Authentication - To verify the signature: + 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. + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. operationId: walletOperationWebhook tags: - Webhooks @@ -57,7 +51,10 @@ post: timestamp: '2026-06-08T14:31:00Z' data: operationId: Operation:019542f5-b3e7-1d02-0000-000000000099 + requestId: Request:9f7a2c10-5e88-4fb1-bd0e-1c3a8e7b2d45 operationType: wallet.export + resourceType: INTERNAL_ACCOUNT + resourceId: InternalAccount:019542f5-b3e7-1d02-0000-000000000005 status: completed failed: summary: Session revoke failed terminally @@ -67,7 +64,10 @@ post: timestamp: '2026-06-08T14:32:00Z' data: operationId: Operation:019542f5-b3e7-1d02-0000-00000000009a + requestId: Request:3c1e5a2f-8b44-4d7a-9e10-6f2b8c4d1a90 operationType: session.revoke + resourceType: SESSION + resourceId: Session:019542f5-b3e7-1d02-0000-00000000009a status: failed error: code: DeleteApiKeysFailed From 925d88ce4d87ad5ee400c68ed5f1d49ecf33b210 Mon Sep 17 00:00:00 2001 From: carsonp6 Date: Fri, 21 Aug 2026 16:47:49 -0700 Subject: [PATCH 4/5] fix(webhooks): stop leaking a Turnkey-shaped code in OperationError's example DeleteApiKeysFailed named a provider activity type in the public spec. Replace it with a Grid-vocabulary placeholder and note that codes are Grid-defined and vendor-stable, since sparkcore doesn't map provider statuses to a Grid taxonomy yet (tracked as part of the 31886-successor emitter work). Co-Authored-By: Claude Fable 5 --- mintlify/openapi.yaml | 9 ++++++--- openapi.yaml | 9 ++++++--- openapi/components/schemas/webhooks/OperationError.yaml | 7 +++++-- openapi/webhooks/wallet-operation.yaml | 2 +- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/mintlify/openapi.yaml b/mintlify/openapi.yaml index 664dd6071..f63785e34 100644 --- a/mintlify/openapi.yaml +++ b/mintlify/openapi.yaml @@ -11843,7 +11843,7 @@ webhooks: resourceId: Session:019542f5-b3e7-1d02-0000-00000000009a status: failed error: - code: DeleteApiKeysFailed + code: OPERATION_FAILED responses: '200': description: Webhook received successfully @@ -25922,8 +25922,11 @@ components: properties: code: type: string - description: Machine-readable failure code for a `FAILED` operation. - example: DeleteApiKeysFailed + description: | + Machine-readable failure code for a `FAILED` operation. Codes are + Grid-defined and stable regardless of which vendor Grid uses under the + hood for a given operation. + example: OPERATION_FAILED WalletOperationFailedData: title: Wallet Operation Failed Data type: object diff --git a/openapi.yaml b/openapi.yaml index 664dd6071..f63785e34 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -11843,7 +11843,7 @@ webhooks: resourceId: Session:019542f5-b3e7-1d02-0000-00000000009a status: failed error: - code: DeleteApiKeysFailed + code: OPERATION_FAILED responses: '200': description: Webhook received successfully @@ -25922,8 +25922,11 @@ components: properties: code: type: string - description: Machine-readable failure code for a `FAILED` operation. - example: DeleteApiKeysFailed + description: | + Machine-readable failure code for a `FAILED` operation. Codes are + Grid-defined and stable regardless of which vendor Grid uses under the + hood for a given operation. + example: OPERATION_FAILED WalletOperationFailedData: title: Wallet Operation Failed Data type: object diff --git a/openapi/components/schemas/webhooks/OperationError.yaml b/openapi/components/schemas/webhooks/OperationError.yaml index 263f99696..5de65c348 100644 --- a/openapi/components/schemas/webhooks/OperationError.yaml +++ b/openapi/components/schemas/webhooks/OperationError.yaml @@ -5,5 +5,8 @@ required: properties: code: type: string - description: Machine-readable failure code for a `FAILED` operation. - example: DeleteApiKeysFailed + description: | + Machine-readable failure code for a `FAILED` operation. Codes are + Grid-defined and stable regardless of which vendor Grid uses under the + hood for a given operation. + example: OPERATION_FAILED diff --git a/openapi/webhooks/wallet-operation.yaml b/openapi/webhooks/wallet-operation.yaml index 12a5a9c33..8dcc7f0c9 100644 --- a/openapi/webhooks/wallet-operation.yaml +++ b/openapi/webhooks/wallet-operation.yaml @@ -70,7 +70,7 @@ post: resourceId: Session:019542f5-b3e7-1d02-0000-00000000009a status: failed error: - code: DeleteApiKeysFailed + code: OPERATION_FAILED responses: '200': description: Webhook received successfully From a665caa0616110b784aca2623d5bdc39988876c4 Mon Sep 17 00:00:00 2001 From: carsonp6 Date: Fri, 21 Aug 2026 16:59:43 -0700 Subject: [PATCH 5/5] feat(webhooks): add auth_credential.create + match error example to the runtime fix - Add `auth_credential.create` to the operationType/resourceType enums: for a create-type operation resourceId is the only way to learn the new credential's id, so its description (and the correlation-model section) is reworded to call that out as resourceId's own primary correlation role, distinct from requestId's. - Update the OperationError example and the failed-webhook sample from the interim OPERATION_FAILED placeholder to SIGNER_PROVIDER_REJECTED, matching the vocabulary sparkcore now actually emits (webdev #33379). Co-Authored-By: Claude Fable 5 --- mintlify/openapi.yaml | 18 +++++++++++------- openapi.yaml | 18 +++++++++++------- .../schemas/webhooks/OperationError.yaml | 2 +- .../webhooks/WalletOperationCompletedData.yaml | 17 +++++++++++------ .../webhooks/WalletOperationFailedData.yaml | 17 +++++++++++------ openapi/webhooks/wallet-operation.yaml | 6 ++++-- 6 files changed, 49 insertions(+), 29 deletions(-) diff --git a/mintlify/openapi.yaml b/mintlify/openapi.yaml index f63785e34..22b3bb0de 100644 --- a/mintlify/openapi.yaml +++ b/mintlify/openapi.yaml @@ -11776,10 +11776,11 @@ webhooks: description: | Webhook that is called when an asynchronous embedded-wallet operation reaches a terminal state. Fires `WALLET_OPERATION.COMPLETED` on terminal success and `WALLET_OPERATION.FAILED` on terminal failure. The payload is self-contained — handle it from the fields below with no follow-up API call needed. - The specific operation is carried in `data.operationType` (`auth_credential.delete`, `session.revoke`, or `wallet.export`), and the business resource it affected is carried in `data.resourceType` / `data.resourceId`: + The specific operation is carried in `data.operationType` (`auth_credential.create`, `auth_credential.delete`, `session.revoke`, or `wallet.export`), and the business resource it affected is carried in `data.resourceType` / `data.resourceId`: | `operationType` | `resourceType` | `resourceId` identifies | | --- | --- | --- | + | `auth_credential.create` | `AUTH_METHOD` | the created credential — its id can't be known before this webhook, since the create request has nothing to echo | | `auth_credential.delete` | `AUTH_METHOD` | the deleted credential | | `session.revoke` | `SESSION` | the revoked session | | `wallet.export` | `INTERNAL_ACCOUNT` | the account whose wallet was exported | @@ -11789,6 +11790,7 @@ webhooks: ### Correlating this webhook - **`data.requestId`** is the primary correlation key. It is the same `Request-Id` value you supplied on the signed retry that produced this terminal result — echo it against the `Request-Id` you sent (and, if you polled through one or more `200 { status: "PROCESSING" }` responses, against the value you kept resending). + - **`data.resourceId`** is the primary correlator for the *business resource* itself, distinct from `requestId`'s role of matching the request. For `auth_credential.create` this is the only way to learn the created credential's id — the request that created it had nothing to echo. - **`id`** (the top-level webhook envelope id) is for deduplication. Grid may redeliver a webhook after a transient failure; track `id` to avoid double-processing. - **`data.operationId`** is a Grid-internal identifier for the operation. It's useful when contacting support about a specific operation, but isn't intended as a correlation key. @@ -11843,7 +11845,7 @@ webhooks: resourceId: Session:019542f5-b3e7-1d02-0000-00000000009a status: failed error: - code: OPERATION_FAILED + code: SIGNER_PROVIDER_REJECTED responses: '200': description: Webhook received successfully @@ -25892,13 +25894,14 @@ components: type: string description: The kind of operation that reached a terminal state. enum: + - auth_credential.create - auth_credential.delete - session.revoke - wallet.export example: wallet.export resourceType: type: string - description: 'The kind of business resource `resourceId` identifies. Determined by `operationType`: `auth_credential.delete` → `AUTH_METHOD`, `session.revoke` → `SESSION`, `wallet.export` → `INTERNAL_ACCOUNT`.' + description: 'The kind of business resource `resourceId` identifies. Determined by `operationType`: `auth_credential.create` and `auth_credential.delete` → `AUTH_METHOD`, `session.revoke` → `SESSION`, `wallet.export` → `INTERNAL_ACCOUNT`.' enum: - AUTH_METHOD - SESSION @@ -25906,7 +25909,7 @@ components: example: INTERNAL_ACCOUNT resourceId: type: string - description: 'LSID of the business resource this operation affected: the deleted `AuthMethod:` for `auth_credential.delete`, the revoked `Session:` for `session.revoke`, or the `InternalAccount:` whose wallet was exported for `wallet.export`.' + description: LSID of the business resource this operation affected. For `auth_credential.create`, this is the **primary way to learn the new credential's id** — the request that created it can't have supplied one in advance. For `auth_credential.delete` and `session.revoke` it echoes the `AuthMethod:` / `Session:` you already knew and referenced in the request; for `wallet.export` it's the `InternalAccount:` whose wallet was exported. example: InternalAccount:019542f5-b3e7-1d02-0000-000000000005 status: type: string @@ -25926,7 +25929,7 @@ components: Machine-readable failure code for a `FAILED` operation. Codes are Grid-defined and stable regardless of which vendor Grid uses under the hood for a given operation. - example: OPERATION_FAILED + example: SIGNER_PROVIDER_REJECTED WalletOperationFailedData: title: Wallet Operation Failed Data type: object @@ -25951,13 +25954,14 @@ components: type: string description: The kind of operation that reached a terminal state. enum: + - auth_credential.create - auth_credential.delete - session.revoke - wallet.export example: wallet.export resourceType: type: string - description: 'The kind of business resource `resourceId` identifies. Determined by `operationType`: `auth_credential.delete` → `AUTH_METHOD`, `session.revoke` → `SESSION`, `wallet.export` → `INTERNAL_ACCOUNT`.' + description: 'The kind of business resource `resourceId` identifies. Determined by `operationType`: `auth_credential.create` and `auth_credential.delete` → `AUTH_METHOD`, `session.revoke` → `SESSION`, `wallet.export` → `INTERNAL_ACCOUNT`.' enum: - AUTH_METHOD - SESSION @@ -25965,7 +25969,7 @@ components: example: SESSION resourceId: type: string - description: 'LSID of the business resource this operation affected: the deleted `AuthMethod:` for `auth_credential.delete`, the revoked `Session:` for `session.revoke`, or the `InternalAccount:` whose wallet was exported for `wallet.export`.' + description: LSID of the business resource this operation affected. For `auth_credential.create`, this is the **primary way to learn the new credential's id** — the request that created it can't have supplied one in advance. For `auth_credential.delete` and `session.revoke` it echoes the `AuthMethod:` / `Session:` you already knew and referenced in the request; for `wallet.export` it's the `InternalAccount:` whose wallet was exported. example: Session:019542f5-b3e7-1d02-0000-00000000009a status: type: string diff --git a/openapi.yaml b/openapi.yaml index f63785e34..22b3bb0de 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -11776,10 +11776,11 @@ webhooks: description: | Webhook that is called when an asynchronous embedded-wallet operation reaches a terminal state. Fires `WALLET_OPERATION.COMPLETED` on terminal success and `WALLET_OPERATION.FAILED` on terminal failure. The payload is self-contained — handle it from the fields below with no follow-up API call needed. - The specific operation is carried in `data.operationType` (`auth_credential.delete`, `session.revoke`, or `wallet.export`), and the business resource it affected is carried in `data.resourceType` / `data.resourceId`: + The specific operation is carried in `data.operationType` (`auth_credential.create`, `auth_credential.delete`, `session.revoke`, or `wallet.export`), and the business resource it affected is carried in `data.resourceType` / `data.resourceId`: | `operationType` | `resourceType` | `resourceId` identifies | | --- | --- | --- | + | `auth_credential.create` | `AUTH_METHOD` | the created credential — its id can't be known before this webhook, since the create request has nothing to echo | | `auth_credential.delete` | `AUTH_METHOD` | the deleted credential | | `session.revoke` | `SESSION` | the revoked session | | `wallet.export` | `INTERNAL_ACCOUNT` | the account whose wallet was exported | @@ -11789,6 +11790,7 @@ webhooks: ### Correlating this webhook - **`data.requestId`** is the primary correlation key. It is the same `Request-Id` value you supplied on the signed retry that produced this terminal result — echo it against the `Request-Id` you sent (and, if you polled through one or more `200 { status: "PROCESSING" }` responses, against the value you kept resending). + - **`data.resourceId`** is the primary correlator for the *business resource* itself, distinct from `requestId`'s role of matching the request. For `auth_credential.create` this is the only way to learn the created credential's id — the request that created it had nothing to echo. - **`id`** (the top-level webhook envelope id) is for deduplication. Grid may redeliver a webhook after a transient failure; track `id` to avoid double-processing. - **`data.operationId`** is a Grid-internal identifier for the operation. It's useful when contacting support about a specific operation, but isn't intended as a correlation key. @@ -11843,7 +11845,7 @@ webhooks: resourceId: Session:019542f5-b3e7-1d02-0000-00000000009a status: failed error: - code: OPERATION_FAILED + code: SIGNER_PROVIDER_REJECTED responses: '200': description: Webhook received successfully @@ -25892,13 +25894,14 @@ components: type: string description: The kind of operation that reached a terminal state. enum: + - auth_credential.create - auth_credential.delete - session.revoke - wallet.export example: wallet.export resourceType: type: string - description: 'The kind of business resource `resourceId` identifies. Determined by `operationType`: `auth_credential.delete` → `AUTH_METHOD`, `session.revoke` → `SESSION`, `wallet.export` → `INTERNAL_ACCOUNT`.' + description: 'The kind of business resource `resourceId` identifies. Determined by `operationType`: `auth_credential.create` and `auth_credential.delete` → `AUTH_METHOD`, `session.revoke` → `SESSION`, `wallet.export` → `INTERNAL_ACCOUNT`.' enum: - AUTH_METHOD - SESSION @@ -25906,7 +25909,7 @@ components: example: INTERNAL_ACCOUNT resourceId: type: string - description: 'LSID of the business resource this operation affected: the deleted `AuthMethod:` for `auth_credential.delete`, the revoked `Session:` for `session.revoke`, or the `InternalAccount:` whose wallet was exported for `wallet.export`.' + description: LSID of the business resource this operation affected. For `auth_credential.create`, this is the **primary way to learn the new credential's id** — the request that created it can't have supplied one in advance. For `auth_credential.delete` and `session.revoke` it echoes the `AuthMethod:` / `Session:` you already knew and referenced in the request; for `wallet.export` it's the `InternalAccount:` whose wallet was exported. example: InternalAccount:019542f5-b3e7-1d02-0000-000000000005 status: type: string @@ -25926,7 +25929,7 @@ components: Machine-readable failure code for a `FAILED` operation. Codes are Grid-defined and stable regardless of which vendor Grid uses under the hood for a given operation. - example: OPERATION_FAILED + example: SIGNER_PROVIDER_REJECTED WalletOperationFailedData: title: Wallet Operation Failed Data type: object @@ -25951,13 +25954,14 @@ components: type: string description: The kind of operation that reached a terminal state. enum: + - auth_credential.create - auth_credential.delete - session.revoke - wallet.export example: wallet.export resourceType: type: string - description: 'The kind of business resource `resourceId` identifies. Determined by `operationType`: `auth_credential.delete` → `AUTH_METHOD`, `session.revoke` → `SESSION`, `wallet.export` → `INTERNAL_ACCOUNT`.' + description: 'The kind of business resource `resourceId` identifies. Determined by `operationType`: `auth_credential.create` and `auth_credential.delete` → `AUTH_METHOD`, `session.revoke` → `SESSION`, `wallet.export` → `INTERNAL_ACCOUNT`.' enum: - AUTH_METHOD - SESSION @@ -25965,7 +25969,7 @@ components: example: SESSION resourceId: type: string - description: 'LSID of the business resource this operation affected: the deleted `AuthMethod:` for `auth_credential.delete`, the revoked `Session:` for `session.revoke`, or the `InternalAccount:` whose wallet was exported for `wallet.export`.' + description: LSID of the business resource this operation affected. For `auth_credential.create`, this is the **primary way to learn the new credential's id** — the request that created it can't have supplied one in advance. For `auth_credential.delete` and `session.revoke` it echoes the `AuthMethod:` / `Session:` you already knew and referenced in the request; for `wallet.export` it's the `InternalAccount:` whose wallet was exported. example: Session:019542f5-b3e7-1d02-0000-00000000009a status: type: string diff --git a/openapi/components/schemas/webhooks/OperationError.yaml b/openapi/components/schemas/webhooks/OperationError.yaml index 5de65c348..ea51753c7 100644 --- a/openapi/components/schemas/webhooks/OperationError.yaml +++ b/openapi/components/schemas/webhooks/OperationError.yaml @@ -9,4 +9,4 @@ properties: Machine-readable failure code for a `FAILED` operation. Codes are Grid-defined and stable regardless of which vendor Grid uses under the hood for a given operation. - example: OPERATION_FAILED + example: SIGNER_PROVIDER_REJECTED diff --git a/openapi/components/schemas/webhooks/WalletOperationCompletedData.yaml b/openapi/components/schemas/webhooks/WalletOperationCompletedData.yaml index f0525430c..0bb04b065 100644 --- a/openapi/components/schemas/webhooks/WalletOperationCompletedData.yaml +++ b/openapi/components/schemas/webhooks/WalletOperationCompletedData.yaml @@ -28,6 +28,7 @@ properties: type: string description: The kind of operation that reached a terminal state. enum: + - auth_credential.create - auth_credential.delete - session.revoke - wallet.export @@ -36,8 +37,9 @@ properties: type: string description: >- The kind of business resource `resourceId` identifies. Determined by - `operationType`: `auth_credential.delete` → `AUTH_METHOD`, - `session.revoke` → `SESSION`, `wallet.export` → `INTERNAL_ACCOUNT`. + `operationType`: `auth_credential.create` and `auth_credential.delete` → + `AUTH_METHOD`, `session.revoke` → `SESSION`, `wallet.export` → + `INTERNAL_ACCOUNT`. enum: - AUTH_METHOD - SESSION @@ -46,10 +48,13 @@ properties: resourceId: type: string description: >- - LSID of the business resource this operation affected: the deleted - `AuthMethod:` for `auth_credential.delete`, the revoked - `Session:` for `session.revoke`, or the `InternalAccount:` - whose wallet was exported for `wallet.export`. + LSID of the business resource this operation affected. For + `auth_credential.create`, this is the **primary way to learn the new + credential's id** — the request that created it can't have supplied + one in advance. For `auth_credential.delete` and `session.revoke` it + echoes the `AuthMethod:` / `Session:` you already knew and + referenced in the request; for `wallet.export` it's the + `InternalAccount:` whose wallet was exported. example: InternalAccount:019542f5-b3e7-1d02-0000-000000000005 status: type: string diff --git a/openapi/components/schemas/webhooks/WalletOperationFailedData.yaml b/openapi/components/schemas/webhooks/WalletOperationFailedData.yaml index beb9975b9..b6bf0ac91 100644 --- a/openapi/components/schemas/webhooks/WalletOperationFailedData.yaml +++ b/openapi/components/schemas/webhooks/WalletOperationFailedData.yaml @@ -29,6 +29,7 @@ properties: type: string description: The kind of operation that reached a terminal state. enum: + - auth_credential.create - auth_credential.delete - session.revoke - wallet.export @@ -37,8 +38,9 @@ properties: type: string description: >- The kind of business resource `resourceId` identifies. Determined by - `operationType`: `auth_credential.delete` → `AUTH_METHOD`, - `session.revoke` → `SESSION`, `wallet.export` → `INTERNAL_ACCOUNT`. + `operationType`: `auth_credential.create` and `auth_credential.delete` → + `AUTH_METHOD`, `session.revoke` → `SESSION`, `wallet.export` → + `INTERNAL_ACCOUNT`. enum: - AUTH_METHOD - SESSION @@ -47,10 +49,13 @@ properties: resourceId: type: string description: >- - LSID of the business resource this operation affected: the deleted - `AuthMethod:` for `auth_credential.delete`, the revoked - `Session:` for `session.revoke`, or the `InternalAccount:` - whose wallet was exported for `wallet.export`. + LSID of the business resource this operation affected. For + `auth_credential.create`, this is the **primary way to learn the new + credential's id** — the request that created it can't have supplied + one in advance. For `auth_credential.delete` and `session.revoke` it + echoes the `AuthMethod:` / `Session:` you already knew and + referenced in the request; for `wallet.export` it's the + `InternalAccount:` whose wallet was exported. example: Session:019542f5-b3e7-1d02-0000-00000000009a status: type: string diff --git a/openapi/webhooks/wallet-operation.yaml b/openapi/webhooks/wallet-operation.yaml index 8dcc7f0c9..427eaddfe 100644 --- a/openapi/webhooks/wallet-operation.yaml +++ b/openapi/webhooks/wallet-operation.yaml @@ -3,10 +3,11 @@ post: description: | Webhook that is called when an asynchronous embedded-wallet operation reaches a terminal state. Fires `WALLET_OPERATION.COMPLETED` on terminal success and `WALLET_OPERATION.FAILED` on terminal failure. The payload is self-contained — handle it from the fields below with no follow-up API call needed. - The specific operation is carried in `data.operationType` (`auth_credential.delete`, `session.revoke`, or `wallet.export`), and the business resource it affected is carried in `data.resourceType` / `data.resourceId`: + The specific operation is carried in `data.operationType` (`auth_credential.create`, `auth_credential.delete`, `session.revoke`, or `wallet.export`), and the business resource it affected is carried in `data.resourceType` / `data.resourceId`: | `operationType` | `resourceType` | `resourceId` identifies | | --- | --- | --- | + | `auth_credential.create` | `AUTH_METHOD` | the created credential — its id can't be known before this webhook, since the create request has nothing to echo | | `auth_credential.delete` | `AUTH_METHOD` | the deleted credential | | `session.revoke` | `SESSION` | the revoked session | | `wallet.export` | `INTERNAL_ACCOUNT` | the account whose wallet was exported | @@ -16,6 +17,7 @@ post: ### Correlating this webhook - **`data.requestId`** is the primary correlation key. It is the same `Request-Id` value you supplied on the signed retry that produced this terminal result — echo it against the `Request-Id` you sent (and, if you polled through one or more `200 { status: "PROCESSING" }` responses, against the value you kept resending). + - **`data.resourceId`** is the primary correlator for the *business resource* itself, distinct from `requestId`'s role of matching the request. For `auth_credential.create` this is the only way to learn the created credential's id — the request that created it had nothing to echo. - **`id`** (the top-level webhook envelope id) is for deduplication. Grid may redeliver a webhook after a transient failure; track `id` to avoid double-processing. - **`data.operationId`** is a Grid-internal identifier for the operation. It's useful when contacting support about a specific operation, but isn't intended as a correlation key. @@ -70,7 +72,7 @@ post: resourceId: Session:019542f5-b3e7-1d02-0000-00000000009a status: failed error: - code: OPERATION_FAILED + code: SIGNER_PROVIDER_REJECTED responses: '200': description: Webhook received successfully