diff --git a/mintlify/openapi.yaml b/mintlify/openapi.yaml index 566f11245..22b3bb0de 100644 --- a/mintlify/openapi.yaml +++ b/mintlify/openapi.yaml @@ -11770,6 +11770,103 @@ 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 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.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 | + + 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). + - **`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. + + 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 + 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 + 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 + requestId: Request:3c1e5a2f-8b44-4d7a-9e10-6f2b8c4d1a90 + operationType: session.revoke + resourceType: SESSION + resourceId: Session:019542f5-b3e7-1d02-0000-00000000009a + status: failed + error: + code: SIGNER_PROVIDER_REJECTED + 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: @@ -25517,6 +25614,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: @@ -25772,6 +25871,138 @@ components: - CARD_TRANSACTION.SETTLED - CARD_TRANSACTION.REFUNDED - CARD_TRANSACTION.EXCEPTION + WalletOperationCompletedData: + title: Wallet Operation Completed Data + type: object + required: + - operationId + - requestId + - operationType + - resourceType + - resourceId + - status + properties: + operationId: + type: string + 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. + 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.create` and `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. 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 + enum: + - completed + description: Terminal status of the operation. + example: completed + OperationError: + type: object + description: Failure details for a terminally failed operation. + required: + - code + properties: + code: + type: string + 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: SIGNER_PROVIDER_REJECTED + WalletOperationFailedData: + title: Wallet Operation Failed Data + type: object + required: + - operationId + - requestId + - operationType + - resourceType + - resourceId + - status + - error + properties: + operationId: + type: string + 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. + 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.create` and `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. 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 + enum: + - failed + description: Terminal status of the operation. + example: failed + error: + $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' + - 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/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 566f11245..22b3bb0de 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -11770,6 +11770,103 @@ 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 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.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 | + + 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). + - **`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. + + 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 + 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 + 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 + requestId: Request:3c1e5a2f-8b44-4d7a-9e10-6f2b8c4d1a90 + operationType: session.revoke + resourceType: SESSION + resourceId: Session:019542f5-b3e7-1d02-0000-00000000009a + status: failed + error: + code: SIGNER_PROVIDER_REJECTED + 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: @@ -25517,6 +25614,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: @@ -25772,6 +25871,138 @@ components: - CARD_TRANSACTION.SETTLED - CARD_TRANSACTION.REFUNDED - CARD_TRANSACTION.EXCEPTION + WalletOperationCompletedData: + title: Wallet Operation Completed Data + type: object + required: + - operationId + - requestId + - operationType + - resourceType + - resourceId + - status + properties: + operationId: + type: string + 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. + 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.create` and `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. 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 + enum: + - completed + description: Terminal status of the operation. + example: completed + OperationError: + type: object + description: Failure details for a terminally failed operation. + required: + - code + properties: + code: + type: string + 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: SIGNER_PROVIDER_REJECTED + WalletOperationFailedData: + title: Wallet Operation Failed Data + type: object + required: + - operationId + - requestId + - operationType + - resourceType + - resourceId + - status + - error + properties: + operationId: + type: string + 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. + 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.create` and `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. 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 + enum: + - failed + description: Terminal status of the operation. + example: failed + error: + $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' + - 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..ea51753c7 --- /dev/null +++ b/openapi/components/schemas/webhooks/OperationError.yaml @@ -0,0 +1,12 @@ +type: object +description: Failure details for a terminally failed operation. +required: + - code +properties: + code: + type: string + 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: SIGNER_PROVIDER_REJECTED diff --git a/openapi/components/schemas/webhooks/WalletOperationCompletedData.yaml b/openapi/components/schemas/webhooks/WalletOperationCompletedData.yaml new file mode 100644 index 000000000..0bb04b065 --- /dev/null +++ b/openapi/components/schemas/webhooks/WalletOperationCompletedData.yaml @@ -0,0 +1,64 @@ +title: Wallet Operation Completed Data +type: object +required: + - operationId + - requestId + - operationType + - resourceType + - resourceId + - status +properties: + operationId: + type: string + 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. + 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.create` and `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. 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 + 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..b6bf0ac91 --- /dev/null +++ b/openapi/components/schemas/webhooks/WalletOperationFailedData.yaml @@ -0,0 +1,67 @@ +title: Wallet Operation Failed Data +type: object +required: + - operationId + - requestId + - operationType + - resourceType + - resourceId + - status + - error +properties: + operationId: + type: string + 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. + 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.create` and `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. 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 + enum: + - failed + description: Terminal status of the operation. + example: failed + error: + $ref: ./OperationError.yaml 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..2094719be --- /dev/null +++ b/openapi/components/schemas/webhooks/WalletOperationWebhookData.yaml @@ -0,0 +1,9 @@ +title: Wallet Operation Data +oneOf: + - $ref: ./WalletOperationCompletedData.yaml + - $ref: ./WalletOperationFailedData.yaml +discriminator: + propertyName: status + mapping: + completed: ./WalletOperationCompletedData.yaml + failed: ./WalletOperationFailedData.yaml diff --git a/openapi/components/schemas/webhooks/WebhookType.yaml b/openapi/components/schemas/webhooks/WebhookType.yaml index df52ceede..c3db69ee7 100644 --- a/openapi/components/schemas/webhooks/WebhookType.yaml +++ b/openapi/components/schemas/webhooks/WebhookType.yaml @@ -42,6 +42,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 8c164aafd..46360d6d9 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -413,6 +413,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..427eaddfe --- /dev/null +++ b/openapi/webhooks/wallet-operation.yaml @@ -0,0 +1,96 @@ +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 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.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 | + + 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). + - **`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. + + 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 + 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 + 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 + requestId: Request:3c1e5a2f-8b44-4d7a-9e10-6f2b8c4d1a90 + operationType: session.revoke + resourceType: SESSION + resourceId: Session:019542f5-b3e7-1d02-0000-00000000009a + status: failed + error: + code: SIGNER_PROVIDER_REJECTED + 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