From a50b908e1b9f67cecc94a9ae4ea418aeff8b0ea8 Mon Sep 17 00:00:00 2001 From: shreyav Date: Wed, 5 Aug 2026 21:54:22 -0700 Subject: [PATCH 1/4] feat: add external account ownership verification Add verify-ownership and verify-ownership/confirm endpoints for customer and platform external accounts, supporting wallet-signature and liveness verification methods. Adds an ownershipVerificationStatus field to ExternalAccount, EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_* webhook types, new error codes, and an expanded ownershipType description. Co-Authored-By: Claude Fable 5 --- mintlify/openapi.yaml | 492 +++++++++++++++++- openapi.yaml | 492 +++++++++++++++++- .../components/schemas/errors/Error400.yaml | 8 + .../external_accounts/ExternalAccount.yaml | 3 + .../external_accounts/OwnershipType.yaml | 8 +- .../OwnershipVerificationConfirmRequest.yaml | 26 + .../OwnershipVerificationMethod.yaml | 12 + .../OwnershipVerificationStart.yaml | 32 ++ .../OwnershipVerificationStartRequest.yaml | 10 + .../OwnershipVerificationStatus.yaml | 17 + .../webhooks/ExternalAccountWebhook.yaml | 14 + .../schemas/webhooks/WebhookType.yaml | 3 + openapi/openapi.yaml | 10 + ..._{externalAccountId}_verify-ownership.yaml | 73 +++ ...alAccountId}_verify-ownership_confirm.yaml | 70 +++ ..._{externalAccountId}_verify-ownership.yaml | 73 +++ ...alAccountId}_verify-ownership_confirm.yaml | 70 +++ openapi/webhooks/external-account.yaml | 110 ++++ 18 files changed, 1519 insertions(+), 4 deletions(-) create mode 100644 openapi/components/schemas/external_accounts/OwnershipVerificationConfirmRequest.yaml create mode 100644 openapi/components/schemas/external_accounts/OwnershipVerificationMethod.yaml create mode 100644 openapi/components/schemas/external_accounts/OwnershipVerificationStart.yaml create mode 100644 openapi/components/schemas/external_accounts/OwnershipVerificationStartRequest.yaml create mode 100644 openapi/components/schemas/external_accounts/OwnershipVerificationStatus.yaml create mode 100644 openapi/components/schemas/webhooks/ExternalAccountWebhook.yaml create mode 100644 openapi/paths/customers/customers_external_accounts_{externalAccountId}_verify-ownership.yaml create mode 100644 openapi/paths/customers/customers_external_accounts_{externalAccountId}_verify-ownership_confirm.yaml create mode 100644 openapi/paths/platform/platform_external_accounts_{externalAccountId}_verify-ownership.yaml create mode 100644 openapi/paths/platform/platform_external_accounts_{externalAccountId}_verify-ownership_confirm.yaml create mode 100644 openapi/webhooks/external-account.yaml diff --git a/mintlify/openapi.yaml b/mintlify/openapi.yaml index 75833f86e..46d0c89d7 100644 --- a/mintlify/openapi.yaml +++ b/mintlify/openapi.yaml @@ -2429,6 +2429,151 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' + /customers/external-accounts/{externalAccountId}/verify-ownership: + parameters: + - name: externalAccountId + in: path + description: The unique identifier of the external account (self-custody crypto wallet) whose ownership is being verified. + required: true + schema: + type: string + post: + summary: Start external account ownership verification + description: | + Begin ownership verification for a `FIRST_PARTY` self-custody crypto wallet + external account. Choose a `method`: + + - `WALLET_SIGNATURE` — the response includes a `messageToSign`; have the + wallet sign it and submit the result to + `POST /customers/external-accounts/{externalAccountId}/verify-ownership/confirm` + to complete verification synchronously. + - `LIVENESS` — the response includes a `verificationLink` (and a `token` + for embedding); the user completes a hosted biometric flow and + verification completes asynchronously. Status transitions are delivered + via `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_*` webhooks and reflected in + the account's `ownershipVerificationStatus`. + + This endpoint is only meaningful for accounts whose + `ownershipVerificationStatus` is `REQUIRED` or `FAILED`. For other accounts, this returns `409`. + operationId: verifyExternalAccountOwnership + tags: + - External Accounts + security: + - BasicAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/OwnershipVerificationStartRequest' + responses: + '200': + description: Ownership verification started; the method-specific material is returned. + content: + application/json: + schema: + $ref: '#/components/schemas/OwnershipVerificationStart' + '400': + description: Invalid request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: Customer or external account not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '409': + description: Ownership verification is not applicable to this external account. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /customers/external-accounts/{externalAccountId}/verify-ownership/confirm: + parameters: + - name: externalAccountId + in: path + description: The unique identifier of the external account (self-custody crypto wallet) whose ownership is being verified. + required: true + schema: + type: string + post: + summary: Confirm external account ownership verification + description: | + Complete a `WALLET_SIGNATURE` ownership verification by submitting the + signature the wallet produced for the `messageToSign` returned by + `POST /customers/external-accounts/{externalAccountId}/verify-ownership`. + The message must be signed exactly as returned, and the signature must be + submitted before the session's `expiresAt`; after expiry, start a new + verification. + + Returns the updated external account, including its new + `ownershipVerificationStatus`. + + This endpoint is only meaningful for accounts with an in-progress + `WALLET_SIGNATURE` verification. For other accounts, this returns `409`. + operationId: confirmExternalAccountOwnershipVerification + tags: + - External Accounts + security: + - BasicAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/OwnershipVerificationConfirmRequest' + responses: + '200': + description: Ownership verification submitted; the updated external account is returned. + content: + application/json: + schema: + $ref: '#/components/schemas/ExternalAccount' + '400': + description: Invalid or expired signature + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: Customer or external account not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '409': + description: Ownership verification is not applicable to this external account. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' /platform/external-accounts: get: summary: List platform external accounts @@ -2633,6 +2778,151 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' + /platform/external-accounts/{externalAccountId}/verify-ownership: + parameters: + - name: externalAccountId + in: path + description: The unique identifier of the platform external account (self-custody crypto wallet) whose ownership is being verified. + required: true + schema: + type: string + post: + summary: Start platform external account ownership verification + description: | + Begin ownership verification for a `FIRST_PARTY` self-custody crypto wallet + external account owned by the platform. Choose a `method`: + + - `WALLET_SIGNATURE` — the response includes a `messageToSign`; have the + wallet sign it and submit the result to + `POST /platform/external-accounts/{externalAccountId}/verify-ownership/confirm` + to complete verification synchronously. + - `LIVENESS` — the response includes a `verificationLink` (and a `token` + for embedding); the user completes a hosted biometric flow and + verification completes asynchronously. Status transitions are delivered + via `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_*` webhooks and reflected in + the account's `ownershipVerificationStatus`. + + This endpoint is only meaningful for accounts whose + `ownershipVerificationStatus` is `REQUIRED` or `FAILED`. For other accounts, this returns `409`. + operationId: verifyPlatformExternalAccountOwnership + tags: + - External Accounts + security: + - BasicAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/OwnershipVerificationStartRequest' + responses: + '200': + description: Ownership verification started; the method-specific material is returned. + content: + application/json: + schema: + $ref: '#/components/schemas/OwnershipVerificationStart' + '400': + description: Invalid request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: External account not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '409': + description: Ownership verification is not applicable to this external account. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /platform/external-accounts/{externalAccountId}/verify-ownership/confirm: + parameters: + - name: externalAccountId + in: path + description: The unique identifier of the platform external account (self-custody crypto wallet) whose ownership is being verified. + required: true + schema: + type: string + post: + summary: Confirm platform external account ownership verification + description: | + Complete a `WALLET_SIGNATURE` ownership verification by submitting the + signature the wallet produced for the `messageToSign` returned by + `POST /platform/external-accounts/{externalAccountId}/verify-ownership`. + The message must be signed exactly as returned, and the signature must be + submitted before the session's `expiresAt`; after expiry, start a new + verification. + + Returns the updated external account, including its new + `ownershipVerificationStatus`. + + This endpoint is only meaningful for accounts with an in-progress + `WALLET_SIGNATURE` verification. For other accounts, this returns `409`. + operationId: confirmPlatformExternalAccountOwnershipVerification + tags: + - External Accounts + security: + - BasicAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/OwnershipVerificationConfirmRequest' + responses: + '200': + description: Ownership verification submitted; the updated external account is returned. + content: + application/json: + schema: + $ref: '#/components/schemas/ExternalAccount' + '400': + description: Invalid or expired signature + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: External account not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '409': + description: Ownership verification is not applicable to this external account. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' /beneficial-owners: post: summary: Create a beneficial owner @@ -10750,6 +11040,93 @@ webhooks: application/json: schema: $ref: '#/components/schemas/Error409' + external-account: + post: + summary: External account ownership verification status change + description: | + Webhook that is called when the ownership verification status of an external account changes. + 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. + + ### Event types + - `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_PENDING_REVIEW` — Fired when a submitted ownership verification enters review. The `data` payload contains the full external account object. + - `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_VERIFIED` — Fired when ownership of the external account has been verified. The `data` payload contains the full external account object. + - `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_FAILED` — Fired when an ownership verification attempt fails; a new verification can be started. The `data` payload contains the full external account object. + operationId: externalAccountWebhook + tags: + - Webhooks + security: + - WebhookSignature: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExternalAccountWebhook' + examples: + ownershipVerified: + summary: Ownership of a self-custody wallet has been verified + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000040 + type: EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_VERIFIED + timestamp: '2025-08-15T14:32:00Z' + data: + id: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + customerId: Customer:da459a29-1fb7-41ce-a4cb-eb3a3c9fd7a7 + status: ACTIVE + currency: USDC + ownershipType: FIRST_PARTY + ownershipVerificationStatus: VERIFIED + accountInfo: + accountType: ETHEREUM_WALLET + address: '0xAbCDEF1234567890aBCdEf1234567890ABcDef12' + ownershipVerificationFailed: + summary: An ownership verification attempt failed + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000041 + type: EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_FAILED + timestamp: '2025-08-15T14:32:00Z' + data: + id: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + customerId: Customer:da459a29-1fb7-41ce-a4cb-eb3a3c9fd7a7 + status: ACTIVE + currency: USDC + ownershipType: FIRST_PARTY + ownershipVerificationStatus: FAILED + accountInfo: + accountType: ETHEREUM_WALLET + address: '0xAbCDEF1234567890aBCdEf1234567890ABcDef12' + 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' verification-update: post: summary: Verification status change @@ -11838,6 +12215,10 @@ components: | STABLECOIN_PROVIDER_ACCOUNT_INVALID | The stablecoin provider account link is not usable | | STABLECOIN_PROVIDER_ACCOUNT_REVOKED | The stablecoin provider account link has been revoked | | STABLECOIN_PROVIDER_ACCOUNT_SELECTION_REQUIRED | Multiple active provider account links exist; pass `stablecoinProviderAccountId` to select one | + | OWNERSHIP_TYPE_REQUIRED | `ownershipType` must be provided for this external account | + | WALLET_VERIFICATION_REQUIRED | The destination account's ownership must be verified before this transfer can proceed | + | THIRD_PARTY_TRANSFER_LIMIT_EXCEEDED | The transfer exceeds the permitted amount for third-party accounts | + | LIGHTNING_TRANSFER_LIMIT_EXCEEDED | The transfer exceeds the permitted amount for this destination type | enum: - INVALID_INPUT - END_USER_TERMS_VERSION_NOT_FOUND @@ -11879,6 +12260,10 @@ components: - STABLECOIN_PROVIDER_ACCOUNT_INVALID - STABLECOIN_PROVIDER_ACCOUNT_REVOKED - STABLECOIN_PROVIDER_ACCOUNT_SELECTION_REQUIRED + - OWNERSHIP_TYPE_REQUIRED + - WALLET_VERIFICATION_REQUIRED + - THIRD_PARTY_TRANSFER_LIMIT_EXCEEDED + - LIGHTNING_TRANSFER_LIMIT_EXCEEDED message: type: string description: Error message @@ -16848,7 +17233,7 @@ components: enum: - FIRST_PARTY - THIRD_PARTY - description: Whether the external account belongs to the customer themselves (first party) or to someone else (third party) + description: Whether the external account belongs to the customer themselves (`FIRST_PARTY`) or to someone else (`THIRD_PARTY`). Required when creating self-custody crypto wallet external accounts on platforms subject to EU Travel Rule requirements; recommended for all other accounts, where providing it can unlock additional capabilities and smoother compliance handling. example: FIRST_PARTY BeneficiaryVerificationStatus: type: string @@ -16877,6 +17262,24 @@ components: type: string description: The verified full name of the account holder as returned by the payment rail example: John Doe + OwnershipVerificationStatus: + type: string + enum: + - NOT_REQUIRED + - REQUIRED + - PENDING_REVIEW + - FAILED + - VERIFIED + description: | + The status of ownership verification for this external account. + + | Status | Description | + |--------|-------------| + | `NOT_REQUIRED` | Ownership verification does not apply to this account | + | `REQUIRED` | Ownership must be verified before transfers above regulatory thresholds can be sent to this account | + | `PENDING_REVIEW` | A verification was submitted and is under review | + | `FAILED` | The most recent verification attempt failed; a new verification can be started | + | `VERIFIED` | Ownership has been verified; no further action is needed | ExternalAccountType: type: string enum: @@ -19236,6 +19639,9 @@ components: beneficiaryVerifiedData: $ref: '#/components/schemas/BeneficiaryVerifiedData' description: Verified beneficiary data returned by the payment rail, if available + ownershipVerificationStatus: + $ref: '#/components/schemas/OwnershipVerificationStatus' + description: The status of ownership verification for this account accountInfo: $ref: '#/components/schemas/ExternalAccountInfoOneOf' ExternalAccountListResponse: @@ -20142,6 +20548,72 @@ components: default: false accountInfo: $ref: '#/components/schemas/ExternalAccountCreateInfoOneOf' + OwnershipVerificationMethod: + type: string + enum: + - WALLET_SIGNATURE + - LIVENESS + description: | + The method used to verify ownership of a self-custody crypto wallet. + + | Method | Description | + |--------|-------------| + | `WALLET_SIGNATURE` | Prove control of the wallet by signing a message with the wallet's key | + | `LIVENESS` | Prove identity via a hosted biometric verification flow | + example: WALLET_SIGNATURE + OwnershipVerificationStartRequest: + type: object + description: Starts ownership verification for a self-custody crypto wallet external account. + required: + - method + properties: + method: + $ref: '#/components/schemas/OwnershipVerificationMethod' + description: The verification method to use. + OwnershipVerificationStart: + type: object + description: 'The material needed to complete an ownership verification. Which fields are populated depends on the requested `method`: `messageToSign` for `WALLET_SIGNATURE`; `verificationLink` and `token` for `LIVENESS`.' + required: + - expiresAt + properties: + messageToSign: + type: string + description: '`WALLET_SIGNATURE` only. The exact message the wallet must sign, character-for-character.' + example: 'I verify that I control this wallet. Nonce: 019542f5-b3e7-1d02' + verificationLink: + type: string + format: uri + description: '`LIVENESS` only. Hosted verification URL to present to the user.' + example: https://verify.example.com/session/019542f5-b3e7-1d02 + token: + type: string + description: '`LIVENESS` only. Access token for embedding the verification flow in the platform''s own UI, as an alternative to `verificationLink`.' + expiresAt: + type: string + format: date-time + description: When this verification session expires. Prompt the user promptly; after expiry, a new verification must be started. + example: '2025-08-15T15:32:00Z' + OwnershipVerificationConfirmRequest: + type: object + description: Completes a `WALLET_SIGNATURE` ownership verification by submitting the signature the wallet produced for the `messageToSign` from the start step. + required: + - signature + - signedAddress + properties: + signature: + type: string + description: The signature produced over the exact `messageToSign` — EIP-191 hex for EVM chains, base64 for Bitcoin, base58-encoded Ed25519 for Solana. + signedAddress: + type: string + description: The wallet address that signed the message. + example: '0xAbCDEF1234567890aBCdEf1234567890ABcDef12' + signatureScheme: + type: string + enum: + - bip137 + - electrum + default: bip137 + description: Bitcoin message-signing format. Defaults to `bip137`; use `electrum` for Electrum/Sparrow wallets. Ignored for non-Bitcoin chains. PlatformExternalAccountCreateRequest: type: object required: @@ -24692,6 +25164,9 @@ components: - VERIFICATION.IN_PROGRESS - VERIFICATION.PENDING_MANUAL_REVIEW - VERIFICATION.READY_FOR_VERIFICATION + - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_PENDING_REVIEW + - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_VERIFIED + - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_FAILED - INTERNAL_ACCOUNT.BALANCE_UPDATED - INTERNAL_ACCOUNT.STATUS_UPDATED - INVITATION.CLAIMED @@ -24886,6 +25361,21 @@ components: enum: - INTERNAL_ACCOUNT.BALANCE_UPDATED - INTERNAL_ACCOUNT.STATUS_UPDATED + ExternalAccountWebhook: + allOf: + - $ref: '#/components/schemas/BaseWebhook' + - type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/ExternalAccount' + type: + type: string + enum: + - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_PENDING_REVIEW + - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_VERIFIED + - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_FAILED VerificationWebhook: allOf: - $ref: '#/components/schemas/BaseWebhook' diff --git a/openapi.yaml b/openapi.yaml index 75833f86e..46d0c89d7 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -2429,6 +2429,151 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' + /customers/external-accounts/{externalAccountId}/verify-ownership: + parameters: + - name: externalAccountId + in: path + description: The unique identifier of the external account (self-custody crypto wallet) whose ownership is being verified. + required: true + schema: + type: string + post: + summary: Start external account ownership verification + description: | + Begin ownership verification for a `FIRST_PARTY` self-custody crypto wallet + external account. Choose a `method`: + + - `WALLET_SIGNATURE` — the response includes a `messageToSign`; have the + wallet sign it and submit the result to + `POST /customers/external-accounts/{externalAccountId}/verify-ownership/confirm` + to complete verification synchronously. + - `LIVENESS` — the response includes a `verificationLink` (and a `token` + for embedding); the user completes a hosted biometric flow and + verification completes asynchronously. Status transitions are delivered + via `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_*` webhooks and reflected in + the account's `ownershipVerificationStatus`. + + This endpoint is only meaningful for accounts whose + `ownershipVerificationStatus` is `REQUIRED` or `FAILED`. For other accounts, this returns `409`. + operationId: verifyExternalAccountOwnership + tags: + - External Accounts + security: + - BasicAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/OwnershipVerificationStartRequest' + responses: + '200': + description: Ownership verification started; the method-specific material is returned. + content: + application/json: + schema: + $ref: '#/components/schemas/OwnershipVerificationStart' + '400': + description: Invalid request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: Customer or external account not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '409': + description: Ownership verification is not applicable to this external account. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /customers/external-accounts/{externalAccountId}/verify-ownership/confirm: + parameters: + - name: externalAccountId + in: path + description: The unique identifier of the external account (self-custody crypto wallet) whose ownership is being verified. + required: true + schema: + type: string + post: + summary: Confirm external account ownership verification + description: | + Complete a `WALLET_SIGNATURE` ownership verification by submitting the + signature the wallet produced for the `messageToSign` returned by + `POST /customers/external-accounts/{externalAccountId}/verify-ownership`. + The message must be signed exactly as returned, and the signature must be + submitted before the session's `expiresAt`; after expiry, start a new + verification. + + Returns the updated external account, including its new + `ownershipVerificationStatus`. + + This endpoint is only meaningful for accounts with an in-progress + `WALLET_SIGNATURE` verification. For other accounts, this returns `409`. + operationId: confirmExternalAccountOwnershipVerification + tags: + - External Accounts + security: + - BasicAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/OwnershipVerificationConfirmRequest' + responses: + '200': + description: Ownership verification submitted; the updated external account is returned. + content: + application/json: + schema: + $ref: '#/components/schemas/ExternalAccount' + '400': + description: Invalid or expired signature + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: Customer or external account not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '409': + description: Ownership verification is not applicable to this external account. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' /platform/external-accounts: get: summary: List platform external accounts @@ -2633,6 +2778,151 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' + /platform/external-accounts/{externalAccountId}/verify-ownership: + parameters: + - name: externalAccountId + in: path + description: The unique identifier of the platform external account (self-custody crypto wallet) whose ownership is being verified. + required: true + schema: + type: string + post: + summary: Start platform external account ownership verification + description: | + Begin ownership verification for a `FIRST_PARTY` self-custody crypto wallet + external account owned by the platform. Choose a `method`: + + - `WALLET_SIGNATURE` — the response includes a `messageToSign`; have the + wallet sign it and submit the result to + `POST /platform/external-accounts/{externalAccountId}/verify-ownership/confirm` + to complete verification synchronously. + - `LIVENESS` — the response includes a `verificationLink` (and a `token` + for embedding); the user completes a hosted biometric flow and + verification completes asynchronously. Status transitions are delivered + via `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_*` webhooks and reflected in + the account's `ownershipVerificationStatus`. + + This endpoint is only meaningful for accounts whose + `ownershipVerificationStatus` is `REQUIRED` or `FAILED`. For other accounts, this returns `409`. + operationId: verifyPlatformExternalAccountOwnership + tags: + - External Accounts + security: + - BasicAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/OwnershipVerificationStartRequest' + responses: + '200': + description: Ownership verification started; the method-specific material is returned. + content: + application/json: + schema: + $ref: '#/components/schemas/OwnershipVerificationStart' + '400': + description: Invalid request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: External account not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '409': + description: Ownership verification is not applicable to this external account. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /platform/external-accounts/{externalAccountId}/verify-ownership/confirm: + parameters: + - name: externalAccountId + in: path + description: The unique identifier of the platform external account (self-custody crypto wallet) whose ownership is being verified. + required: true + schema: + type: string + post: + summary: Confirm platform external account ownership verification + description: | + Complete a `WALLET_SIGNATURE` ownership verification by submitting the + signature the wallet produced for the `messageToSign` returned by + `POST /platform/external-accounts/{externalAccountId}/verify-ownership`. + The message must be signed exactly as returned, and the signature must be + submitted before the session's `expiresAt`; after expiry, start a new + verification. + + Returns the updated external account, including its new + `ownershipVerificationStatus`. + + This endpoint is only meaningful for accounts with an in-progress + `WALLET_SIGNATURE` verification. For other accounts, this returns `409`. + operationId: confirmPlatformExternalAccountOwnershipVerification + tags: + - External Accounts + security: + - BasicAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/OwnershipVerificationConfirmRequest' + responses: + '200': + description: Ownership verification submitted; the updated external account is returned. + content: + application/json: + schema: + $ref: '#/components/schemas/ExternalAccount' + '400': + description: Invalid or expired signature + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: External account not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '409': + description: Ownership verification is not applicable to this external account. + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' /beneficial-owners: post: summary: Create a beneficial owner @@ -10750,6 +11040,93 @@ webhooks: application/json: schema: $ref: '#/components/schemas/Error409' + external-account: + post: + summary: External account ownership verification status change + description: | + Webhook that is called when the ownership verification status of an external account changes. + 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. + + ### Event types + - `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_PENDING_REVIEW` — Fired when a submitted ownership verification enters review. The `data` payload contains the full external account object. + - `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_VERIFIED` — Fired when ownership of the external account has been verified. The `data` payload contains the full external account object. + - `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_FAILED` — Fired when an ownership verification attempt fails; a new verification can be started. The `data` payload contains the full external account object. + operationId: externalAccountWebhook + tags: + - Webhooks + security: + - WebhookSignature: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExternalAccountWebhook' + examples: + ownershipVerified: + summary: Ownership of a self-custody wallet has been verified + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000040 + type: EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_VERIFIED + timestamp: '2025-08-15T14:32:00Z' + data: + id: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + customerId: Customer:da459a29-1fb7-41ce-a4cb-eb3a3c9fd7a7 + status: ACTIVE + currency: USDC + ownershipType: FIRST_PARTY + ownershipVerificationStatus: VERIFIED + accountInfo: + accountType: ETHEREUM_WALLET + address: '0xAbCDEF1234567890aBCdEf1234567890ABcDef12' + ownershipVerificationFailed: + summary: An ownership verification attempt failed + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000041 + type: EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_FAILED + timestamp: '2025-08-15T14:32:00Z' + data: + id: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + customerId: Customer:da459a29-1fb7-41ce-a4cb-eb3a3c9fd7a7 + status: ACTIVE + currency: USDC + ownershipType: FIRST_PARTY + ownershipVerificationStatus: FAILED + accountInfo: + accountType: ETHEREUM_WALLET + address: '0xAbCDEF1234567890aBCdEf1234567890ABcDef12' + 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' verification-update: post: summary: Verification status change @@ -11838,6 +12215,10 @@ components: | STABLECOIN_PROVIDER_ACCOUNT_INVALID | The stablecoin provider account link is not usable | | STABLECOIN_PROVIDER_ACCOUNT_REVOKED | The stablecoin provider account link has been revoked | | STABLECOIN_PROVIDER_ACCOUNT_SELECTION_REQUIRED | Multiple active provider account links exist; pass `stablecoinProviderAccountId` to select one | + | OWNERSHIP_TYPE_REQUIRED | `ownershipType` must be provided for this external account | + | WALLET_VERIFICATION_REQUIRED | The destination account's ownership must be verified before this transfer can proceed | + | THIRD_PARTY_TRANSFER_LIMIT_EXCEEDED | The transfer exceeds the permitted amount for third-party accounts | + | LIGHTNING_TRANSFER_LIMIT_EXCEEDED | The transfer exceeds the permitted amount for this destination type | enum: - INVALID_INPUT - END_USER_TERMS_VERSION_NOT_FOUND @@ -11879,6 +12260,10 @@ components: - STABLECOIN_PROVIDER_ACCOUNT_INVALID - STABLECOIN_PROVIDER_ACCOUNT_REVOKED - STABLECOIN_PROVIDER_ACCOUNT_SELECTION_REQUIRED + - OWNERSHIP_TYPE_REQUIRED + - WALLET_VERIFICATION_REQUIRED + - THIRD_PARTY_TRANSFER_LIMIT_EXCEEDED + - LIGHTNING_TRANSFER_LIMIT_EXCEEDED message: type: string description: Error message @@ -16848,7 +17233,7 @@ components: enum: - FIRST_PARTY - THIRD_PARTY - description: Whether the external account belongs to the customer themselves (first party) or to someone else (third party) + description: Whether the external account belongs to the customer themselves (`FIRST_PARTY`) or to someone else (`THIRD_PARTY`). Required when creating self-custody crypto wallet external accounts on platforms subject to EU Travel Rule requirements; recommended for all other accounts, where providing it can unlock additional capabilities and smoother compliance handling. example: FIRST_PARTY BeneficiaryVerificationStatus: type: string @@ -16877,6 +17262,24 @@ components: type: string description: The verified full name of the account holder as returned by the payment rail example: John Doe + OwnershipVerificationStatus: + type: string + enum: + - NOT_REQUIRED + - REQUIRED + - PENDING_REVIEW + - FAILED + - VERIFIED + description: | + The status of ownership verification for this external account. + + | Status | Description | + |--------|-------------| + | `NOT_REQUIRED` | Ownership verification does not apply to this account | + | `REQUIRED` | Ownership must be verified before transfers above regulatory thresholds can be sent to this account | + | `PENDING_REVIEW` | A verification was submitted and is under review | + | `FAILED` | The most recent verification attempt failed; a new verification can be started | + | `VERIFIED` | Ownership has been verified; no further action is needed | ExternalAccountType: type: string enum: @@ -19236,6 +19639,9 @@ components: beneficiaryVerifiedData: $ref: '#/components/schemas/BeneficiaryVerifiedData' description: Verified beneficiary data returned by the payment rail, if available + ownershipVerificationStatus: + $ref: '#/components/schemas/OwnershipVerificationStatus' + description: The status of ownership verification for this account accountInfo: $ref: '#/components/schemas/ExternalAccountInfoOneOf' ExternalAccountListResponse: @@ -20142,6 +20548,72 @@ components: default: false accountInfo: $ref: '#/components/schemas/ExternalAccountCreateInfoOneOf' + OwnershipVerificationMethod: + type: string + enum: + - WALLET_SIGNATURE + - LIVENESS + description: | + The method used to verify ownership of a self-custody crypto wallet. + + | Method | Description | + |--------|-------------| + | `WALLET_SIGNATURE` | Prove control of the wallet by signing a message with the wallet's key | + | `LIVENESS` | Prove identity via a hosted biometric verification flow | + example: WALLET_SIGNATURE + OwnershipVerificationStartRequest: + type: object + description: Starts ownership verification for a self-custody crypto wallet external account. + required: + - method + properties: + method: + $ref: '#/components/schemas/OwnershipVerificationMethod' + description: The verification method to use. + OwnershipVerificationStart: + type: object + description: 'The material needed to complete an ownership verification. Which fields are populated depends on the requested `method`: `messageToSign` for `WALLET_SIGNATURE`; `verificationLink` and `token` for `LIVENESS`.' + required: + - expiresAt + properties: + messageToSign: + type: string + description: '`WALLET_SIGNATURE` only. The exact message the wallet must sign, character-for-character.' + example: 'I verify that I control this wallet. Nonce: 019542f5-b3e7-1d02' + verificationLink: + type: string + format: uri + description: '`LIVENESS` only. Hosted verification URL to present to the user.' + example: https://verify.example.com/session/019542f5-b3e7-1d02 + token: + type: string + description: '`LIVENESS` only. Access token for embedding the verification flow in the platform''s own UI, as an alternative to `verificationLink`.' + expiresAt: + type: string + format: date-time + description: When this verification session expires. Prompt the user promptly; after expiry, a new verification must be started. + example: '2025-08-15T15:32:00Z' + OwnershipVerificationConfirmRequest: + type: object + description: Completes a `WALLET_SIGNATURE` ownership verification by submitting the signature the wallet produced for the `messageToSign` from the start step. + required: + - signature + - signedAddress + properties: + signature: + type: string + description: The signature produced over the exact `messageToSign` — EIP-191 hex for EVM chains, base64 for Bitcoin, base58-encoded Ed25519 for Solana. + signedAddress: + type: string + description: The wallet address that signed the message. + example: '0xAbCDEF1234567890aBCdEf1234567890ABcDef12' + signatureScheme: + type: string + enum: + - bip137 + - electrum + default: bip137 + description: Bitcoin message-signing format. Defaults to `bip137`; use `electrum` for Electrum/Sparrow wallets. Ignored for non-Bitcoin chains. PlatformExternalAccountCreateRequest: type: object required: @@ -24692,6 +25164,9 @@ components: - VERIFICATION.IN_PROGRESS - VERIFICATION.PENDING_MANUAL_REVIEW - VERIFICATION.READY_FOR_VERIFICATION + - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_PENDING_REVIEW + - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_VERIFIED + - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_FAILED - INTERNAL_ACCOUNT.BALANCE_UPDATED - INTERNAL_ACCOUNT.STATUS_UPDATED - INVITATION.CLAIMED @@ -24886,6 +25361,21 @@ components: enum: - INTERNAL_ACCOUNT.BALANCE_UPDATED - INTERNAL_ACCOUNT.STATUS_UPDATED + ExternalAccountWebhook: + allOf: + - $ref: '#/components/schemas/BaseWebhook' + - type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/ExternalAccount' + type: + type: string + enum: + - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_PENDING_REVIEW + - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_VERIFIED + - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_FAILED VerificationWebhook: allOf: - $ref: '#/components/schemas/BaseWebhook' diff --git a/openapi/components/schemas/errors/Error400.yaml b/openapi/components/schemas/errors/Error400.yaml index e38f562c2..dc073dccd 100644 --- a/openapi/components/schemas/errors/Error400.yaml +++ b/openapi/components/schemas/errors/Error400.yaml @@ -54,6 +54,10 @@ properties: | STABLECOIN_PROVIDER_ACCOUNT_INVALID | The stablecoin provider account link is not usable | | STABLECOIN_PROVIDER_ACCOUNT_REVOKED | The stablecoin provider account link has been revoked | | STABLECOIN_PROVIDER_ACCOUNT_SELECTION_REQUIRED | Multiple active provider account links exist; pass `stablecoinProviderAccountId` to select one | + | OWNERSHIP_TYPE_REQUIRED | `ownershipType` must be provided for this external account | + | WALLET_VERIFICATION_REQUIRED | The destination account's ownership must be verified before this transfer can proceed | + | THIRD_PARTY_TRANSFER_LIMIT_EXCEEDED | The transfer exceeds the permitted amount for third-party accounts | + | LIGHTNING_TRANSFER_LIMIT_EXCEEDED | The transfer exceeds the permitted amount for this destination type | enum: - INVALID_INPUT - END_USER_TERMS_VERSION_NOT_FOUND @@ -95,6 +99,10 @@ properties: - STABLECOIN_PROVIDER_ACCOUNT_INVALID - STABLECOIN_PROVIDER_ACCOUNT_REVOKED - STABLECOIN_PROVIDER_ACCOUNT_SELECTION_REQUIRED + - OWNERSHIP_TYPE_REQUIRED + - WALLET_VERIFICATION_REQUIRED + - THIRD_PARTY_TRANSFER_LIMIT_EXCEEDED + - LIGHTNING_TRANSFER_LIMIT_EXCEEDED message: type: string description: Error message diff --git a/openapi/components/schemas/external_accounts/ExternalAccount.yaml b/openapi/components/schemas/external_accounts/ExternalAccount.yaml index 08449b833..aa2099cdb 100644 --- a/openapi/components/schemas/external_accounts/ExternalAccount.yaml +++ b/openapi/components/schemas/external_accounts/ExternalAccount.yaml @@ -42,5 +42,8 @@ allOf: beneficiaryVerifiedData: $ref: ./BeneficiaryVerifiedData.yaml description: Verified beneficiary data returned by the payment rail, if available + ownershipVerificationStatus: + $ref: ./OwnershipVerificationStatus.yaml + description: The status of ownership verification for this account accountInfo: $ref: ./ExternalAccountInfoOneOf.yaml diff --git a/openapi/components/schemas/external_accounts/OwnershipType.yaml b/openapi/components/schemas/external_accounts/OwnershipType.yaml index e2b6bce03..6f369994e 100644 --- a/openapi/components/schemas/external_accounts/OwnershipType.yaml +++ b/openapi/components/schemas/external_accounts/OwnershipType.yaml @@ -3,6 +3,10 @@ enum: - FIRST_PARTY - THIRD_PARTY description: >- - Whether the external account belongs to the customer themselves (first party) - or to someone else (third party) + Whether the external account belongs to the customer themselves + (`FIRST_PARTY`) or to someone else (`THIRD_PARTY`). Required when creating + self-custody crypto wallet external accounts on platforms subject to EU + Travel Rule requirements; recommended for all other accounts, where + providing it can unlock additional capabilities and smoother compliance + handling. example: FIRST_PARTY diff --git a/openapi/components/schemas/external_accounts/OwnershipVerificationConfirmRequest.yaml b/openapi/components/schemas/external_accounts/OwnershipVerificationConfirmRequest.yaml new file mode 100644 index 000000000..25c675387 --- /dev/null +++ b/openapi/components/schemas/external_accounts/OwnershipVerificationConfirmRequest.yaml @@ -0,0 +1,26 @@ +type: object +description: >- + Completes a `WALLET_SIGNATURE` ownership verification by submitting the + signature the wallet produced for the `messageToSign` from the start step. +required: + - signature + - signedAddress +properties: + signature: + type: string + description: >- + The signature produced over the exact `messageToSign` — EIP-191 hex for + EVM chains, base64 for Bitcoin, base58-encoded Ed25519 for Solana. + signedAddress: + type: string + description: The wallet address that signed the message. + example: '0xAbCDEF1234567890aBCdEf1234567890ABcDef12' + signatureScheme: + type: string + enum: + - bip137 + - electrum + default: bip137 + description: >- + Bitcoin message-signing format. Defaults to `bip137`; use `electrum` for + Electrum/Sparrow wallets. Ignored for non-Bitcoin chains. diff --git a/openapi/components/schemas/external_accounts/OwnershipVerificationMethod.yaml b/openapi/components/schemas/external_accounts/OwnershipVerificationMethod.yaml new file mode 100644 index 000000000..6211b5928 --- /dev/null +++ b/openapi/components/schemas/external_accounts/OwnershipVerificationMethod.yaml @@ -0,0 +1,12 @@ +type: string +enum: + - WALLET_SIGNATURE + - LIVENESS +description: | + The method used to verify ownership of a self-custody crypto wallet. + + | Method | Description | + |--------|-------------| + | `WALLET_SIGNATURE` | Prove control of the wallet by signing a message with the wallet's key | + | `LIVENESS` | Prove identity via a hosted biometric verification flow | +example: WALLET_SIGNATURE diff --git a/openapi/components/schemas/external_accounts/OwnershipVerificationStart.yaml b/openapi/components/schemas/external_accounts/OwnershipVerificationStart.yaml new file mode 100644 index 000000000..2987e0b97 --- /dev/null +++ b/openapi/components/schemas/external_accounts/OwnershipVerificationStart.yaml @@ -0,0 +1,32 @@ +type: object +description: >- + The material needed to complete an ownership verification. Which fields are + populated depends on the requested `method`: `messageToSign` for + `WALLET_SIGNATURE`; `verificationLink` and `token` for `LIVENESS`. +required: + - expiresAt +properties: + messageToSign: + type: string + description: >- + `WALLET_SIGNATURE` only. The exact message the wallet must sign, + character-for-character. + example: 'I verify that I control this wallet. Nonce: 019542f5-b3e7-1d02' + verificationLink: + type: string + format: uri + description: >- + `LIVENESS` only. Hosted verification URL to present to the user. + example: https://verify.example.com/session/019542f5-b3e7-1d02 + token: + type: string + description: >- + `LIVENESS` only. Access token for embedding the verification flow in the + platform's own UI, as an alternative to `verificationLink`. + expiresAt: + type: string + format: date-time + description: >- + When this verification session expires. Prompt the user promptly; after + expiry, a new verification must be started. + example: '2025-08-15T15:32:00Z' diff --git a/openapi/components/schemas/external_accounts/OwnershipVerificationStartRequest.yaml b/openapi/components/schemas/external_accounts/OwnershipVerificationStartRequest.yaml new file mode 100644 index 000000000..447fc9da4 --- /dev/null +++ b/openapi/components/schemas/external_accounts/OwnershipVerificationStartRequest.yaml @@ -0,0 +1,10 @@ +type: object +description: >- + Starts ownership verification for a self-custody crypto wallet external + account. +required: + - method +properties: + method: + $ref: ./OwnershipVerificationMethod.yaml + description: The verification method to use. diff --git a/openapi/components/schemas/external_accounts/OwnershipVerificationStatus.yaml b/openapi/components/schemas/external_accounts/OwnershipVerificationStatus.yaml new file mode 100644 index 000000000..199c3ffa0 --- /dev/null +++ b/openapi/components/schemas/external_accounts/OwnershipVerificationStatus.yaml @@ -0,0 +1,17 @@ +type: string +enum: + - NOT_REQUIRED + - REQUIRED + - PENDING_REVIEW + - FAILED + - VERIFIED +description: | + The status of ownership verification for this external account. + + | Status | Description | + |--------|-------------| + | `NOT_REQUIRED` | Ownership verification does not apply to this account | + | `REQUIRED` | Ownership must be verified before transfers above regulatory thresholds can be sent to this account | + | `PENDING_REVIEW` | A verification was submitted and is under review | + | `FAILED` | The most recent verification attempt failed; a new verification can be started | + | `VERIFIED` | Ownership has been verified; no further action is needed | diff --git a/openapi/components/schemas/webhooks/ExternalAccountWebhook.yaml b/openapi/components/schemas/webhooks/ExternalAccountWebhook.yaml new file mode 100644 index 000000000..23006152b --- /dev/null +++ b/openapi/components/schemas/webhooks/ExternalAccountWebhook.yaml @@ -0,0 +1,14 @@ +allOf: + - $ref: ./BaseWebhook.yaml + - type: object + required: + - data + properties: + data: + $ref: ../external_accounts/ExternalAccount.yaml + type: + type: string + enum: + - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_PENDING_REVIEW + - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_VERIFIED + - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_FAILED diff --git a/openapi/components/schemas/webhooks/WebhookType.yaml b/openapi/components/schemas/webhooks/WebhookType.yaml index fab72c524..b6ec84ded 100644 --- a/openapi/components/schemas/webhooks/WebhookType.yaml +++ b/openapi/components/schemas/webhooks/WebhookType.yaml @@ -26,6 +26,9 @@ enum: - VERIFICATION.IN_PROGRESS - VERIFICATION.PENDING_MANUAL_REVIEW - VERIFICATION.READY_FOR_VERIFICATION + - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_PENDING_REVIEW + - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_VERIFIED + - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_FAILED - INTERNAL_ACCOUNT.BALANCE_UPDATED - INTERNAL_ACCOUNT.STATUS_UPDATED - INVITATION.CLAIMED diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index b6143351f..db1c9dd0d 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -189,10 +189,18 @@ paths: $ref: paths/customers/customers_external_accounts.yaml /customers/external-accounts/{externalAccountId}: $ref: paths/customers/customers_external_accounts_{externalAccountId}.yaml + /customers/external-accounts/{externalAccountId}/verify-ownership: + $ref: paths/customers/customers_external_accounts_{externalAccountId}_verify-ownership.yaml + /customers/external-accounts/{externalAccountId}/verify-ownership/confirm: + $ref: paths/customers/customers_external_accounts_{externalAccountId}_verify-ownership_confirm.yaml /platform/external-accounts: $ref: paths/platform/platform_external_accounts.yaml /platform/external-accounts/{externalAccountId}: $ref: paths/platform/platform_external_accounts_{externalAccountId}.yaml + /platform/external-accounts/{externalAccountId}/verify-ownership: + $ref: paths/platform/platform_external_accounts_{externalAccountId}_verify-ownership.yaml + /platform/external-accounts/{externalAccountId}/verify-ownership/confirm: + $ref: paths/platform/platform_external_accounts_{externalAccountId}_verify-ownership_confirm.yaml /beneficial-owners: $ref: paths/beneficial-owners/beneficial_owners.yaml /beneficial-owners/{beneficialOwnerId}: @@ -389,6 +397,8 @@ webhooks: $ref: webhooks/customer-update.yaml internal-account-status: $ref: webhooks/internal-account-status.yaml + external-account: + $ref: webhooks/external-account.yaml verification-update: $ref: webhooks/verification-update.yaml card-state-change: diff --git a/openapi/paths/customers/customers_external_accounts_{externalAccountId}_verify-ownership.yaml b/openapi/paths/customers/customers_external_accounts_{externalAccountId}_verify-ownership.yaml new file mode 100644 index 000000000..2a9dcceec --- /dev/null +++ b/openapi/paths/customers/customers_external_accounts_{externalAccountId}_verify-ownership.yaml @@ -0,0 +1,73 @@ +parameters: + - name: externalAccountId + in: path + description: The unique identifier of the external account (self-custody crypto wallet) whose ownership is being verified. + required: true + schema: + type: string +post: + summary: Start external account ownership verification + description: | + Begin ownership verification for a `FIRST_PARTY` self-custody crypto wallet + external account. Choose a `method`: + + - `WALLET_SIGNATURE` — the response includes a `messageToSign`; have the + wallet sign it and submit the result to + `POST /customers/external-accounts/{externalAccountId}/verify-ownership/confirm` + to complete verification synchronously. + - `LIVENESS` — the response includes a `verificationLink` (and a `token` + for embedding); the user completes a hosted biometric flow and + verification completes asynchronously. Status transitions are delivered + via `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_*` webhooks and reflected in + the account's `ownershipVerificationStatus`. + + This endpoint is only meaningful for accounts whose + `ownershipVerificationStatus` is `REQUIRED` or `FAILED`. For other accounts, this returns `409`. + operationId: verifyExternalAccountOwnership + tags: + - External Accounts + security: + - BasicAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: ../../components/schemas/external_accounts/OwnershipVerificationStartRequest.yaml + responses: + '200': + description: Ownership verification started; the method-specific material is returned. + content: + application/json: + schema: + $ref: ../../components/schemas/external_accounts/OwnershipVerificationStart.yaml + '400': + description: Invalid request + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error400.yaml + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error401.yaml + '404': + description: Customer or external account not found + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error404.yaml + '409': + description: Ownership verification is not applicable to this external account. + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error409.yaml + '500': + description: Internal service error + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error500.yaml diff --git a/openapi/paths/customers/customers_external_accounts_{externalAccountId}_verify-ownership_confirm.yaml b/openapi/paths/customers/customers_external_accounts_{externalAccountId}_verify-ownership_confirm.yaml new file mode 100644 index 000000000..e5895f3f3 --- /dev/null +++ b/openapi/paths/customers/customers_external_accounts_{externalAccountId}_verify-ownership_confirm.yaml @@ -0,0 +1,70 @@ +parameters: + - name: externalAccountId + in: path + description: The unique identifier of the external account (self-custody crypto wallet) whose ownership is being verified. + required: true + schema: + type: string +post: + summary: Confirm external account ownership verification + description: | + Complete a `WALLET_SIGNATURE` ownership verification by submitting the + signature the wallet produced for the `messageToSign` returned by + `POST /customers/external-accounts/{externalAccountId}/verify-ownership`. + The message must be signed exactly as returned, and the signature must be + submitted before the session's `expiresAt`; after expiry, start a new + verification. + + Returns the updated external account, including its new + `ownershipVerificationStatus`. + + This endpoint is only meaningful for accounts with an in-progress + `WALLET_SIGNATURE` verification. For other accounts, this returns `409`. + operationId: confirmExternalAccountOwnershipVerification + tags: + - External Accounts + security: + - BasicAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: ../../components/schemas/external_accounts/OwnershipVerificationConfirmRequest.yaml + responses: + '200': + description: Ownership verification submitted; the updated external account is returned. + content: + application/json: + schema: + $ref: ../../components/schemas/external_accounts/ExternalAccount.yaml + '400': + description: Invalid or expired signature + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error400.yaml + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error401.yaml + '404': + description: Customer or external account not found + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error404.yaml + '409': + description: Ownership verification is not applicable to this external account. + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error409.yaml + '500': + description: Internal service error + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error500.yaml diff --git a/openapi/paths/platform/platform_external_accounts_{externalAccountId}_verify-ownership.yaml b/openapi/paths/platform/platform_external_accounts_{externalAccountId}_verify-ownership.yaml new file mode 100644 index 000000000..993e275dc --- /dev/null +++ b/openapi/paths/platform/platform_external_accounts_{externalAccountId}_verify-ownership.yaml @@ -0,0 +1,73 @@ +parameters: + - name: externalAccountId + in: path + description: The unique identifier of the platform external account (self-custody crypto wallet) whose ownership is being verified. + required: true + schema: + type: string +post: + summary: Start platform external account ownership verification + description: | + Begin ownership verification for a `FIRST_PARTY` self-custody crypto wallet + external account owned by the platform. Choose a `method`: + + - `WALLET_SIGNATURE` — the response includes a `messageToSign`; have the + wallet sign it and submit the result to + `POST /platform/external-accounts/{externalAccountId}/verify-ownership/confirm` + to complete verification synchronously. + - `LIVENESS` — the response includes a `verificationLink` (and a `token` + for embedding); the user completes a hosted biometric flow and + verification completes asynchronously. Status transitions are delivered + via `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_*` webhooks and reflected in + the account's `ownershipVerificationStatus`. + + This endpoint is only meaningful for accounts whose + `ownershipVerificationStatus` is `REQUIRED` or `FAILED`. For other accounts, this returns `409`. + operationId: verifyPlatformExternalAccountOwnership + tags: + - External Accounts + security: + - BasicAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: ../../components/schemas/external_accounts/OwnershipVerificationStartRequest.yaml + responses: + '200': + description: Ownership verification started; the method-specific material is returned. + content: + application/json: + schema: + $ref: ../../components/schemas/external_accounts/OwnershipVerificationStart.yaml + '400': + description: Invalid request + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error400.yaml + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error401.yaml + '404': + description: External account not found + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error404.yaml + '409': + description: Ownership verification is not applicable to this external account. + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error409.yaml + '500': + description: Internal service error + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error500.yaml diff --git a/openapi/paths/platform/platform_external_accounts_{externalAccountId}_verify-ownership_confirm.yaml b/openapi/paths/platform/platform_external_accounts_{externalAccountId}_verify-ownership_confirm.yaml new file mode 100644 index 000000000..48b04e11f --- /dev/null +++ b/openapi/paths/platform/platform_external_accounts_{externalAccountId}_verify-ownership_confirm.yaml @@ -0,0 +1,70 @@ +parameters: + - name: externalAccountId + in: path + description: The unique identifier of the platform external account (self-custody crypto wallet) whose ownership is being verified. + required: true + schema: + type: string +post: + summary: Confirm platform external account ownership verification + description: | + Complete a `WALLET_SIGNATURE` ownership verification by submitting the + signature the wallet produced for the `messageToSign` returned by + `POST /platform/external-accounts/{externalAccountId}/verify-ownership`. + The message must be signed exactly as returned, and the signature must be + submitted before the session's `expiresAt`; after expiry, start a new + verification. + + Returns the updated external account, including its new + `ownershipVerificationStatus`. + + This endpoint is only meaningful for accounts with an in-progress + `WALLET_SIGNATURE` verification. For other accounts, this returns `409`. + operationId: confirmPlatformExternalAccountOwnershipVerification + tags: + - External Accounts + security: + - BasicAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: ../../components/schemas/external_accounts/OwnershipVerificationConfirmRequest.yaml + responses: + '200': + description: Ownership verification submitted; the updated external account is returned. + content: + application/json: + schema: + $ref: ../../components/schemas/external_accounts/ExternalAccount.yaml + '400': + description: Invalid or expired signature + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error400.yaml + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error401.yaml + '404': + description: External account not found + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error404.yaml + '409': + description: Ownership verification is not applicable to this external account. + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error409.yaml + '500': + description: Internal service error + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error500.yaml diff --git a/openapi/webhooks/external-account.yaml b/openapi/webhooks/external-account.yaml new file mode 100644 index 000000000..c969e5cf6 --- /dev/null +++ b/openapi/webhooks/external-account.yaml @@ -0,0 +1,110 @@ +post: + summary: External account ownership verification status change + description: > + Webhook that is called when the ownership verification status of an + external account changes. + + 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. + + + ### Event types + + - `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_PENDING_REVIEW` — Fired when a + submitted ownership verification enters review. The `data` payload contains + the full external account object. + + - `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_VERIFIED` — Fired when ownership + of the external account has been verified. The `data` payload contains the + full external account object. + + - `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_FAILED` — Fired when an + ownership verification attempt fails; a new verification can be started. + The `data` payload contains the full external account object. + + + operationId: externalAccountWebhook + tags: + - Webhooks + security: + - WebhookSignature: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: ../components/schemas/webhooks/ExternalAccountWebhook.yaml + examples: + ownershipVerified: + summary: Ownership of a self-custody wallet has been verified + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000040 + type: EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_VERIFIED + timestamp: '2025-08-15T14:32:00Z' + data: + id: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + customerId: Customer:da459a29-1fb7-41ce-a4cb-eb3a3c9fd7a7 + status: ACTIVE + currency: USDC + ownershipType: FIRST_PARTY + ownershipVerificationStatus: VERIFIED + accountInfo: + accountType: ETHEREUM_WALLET + address: '0xAbCDEF1234567890aBCdEf1234567890ABcDef12' + ownershipVerificationFailed: + summary: An ownership verification attempt failed + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000041 + type: EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_FAILED + timestamp: '2025-08-15T14:32:00Z' + data: + id: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + customerId: Customer:da459a29-1fb7-41ce-a4cb-eb3a3c9fd7a7 + status: ACTIVE + currency: USDC + ownershipType: FIRST_PARTY + ownershipVerificationStatus: FAILED + accountInfo: + accountType: ETHEREUM_WALLET + address: '0xAbCDEF1234567890aBCdEf1234567890ABcDef12' + 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 e2029e17f1d5693b1906fc5dbf8a90a01f0b4b98 Mon Sep 17 00:00:00 2001 From: shreyav Date: Wed, 5 Aug 2026 22:01:21 -0700 Subject: [PATCH 2/4] Add creation-time ownership-verification webhook types for lifecycle consistency Payments fire webhooks for their creation-time state (OUTGOING_PAYMENT.PENDING), so ownership verification does the same: NOT_REQUIRED and REQUIRED join the async transitions, giving webhook consumers the full lifecycle. Co-Authored-By: Claude Fable 5 --- mintlify/openapi.yaml | 6 ++++++ openapi.yaml | 6 ++++++ .../schemas/webhooks/ExternalAccountWebhook.yaml | 2 ++ openapi/components/schemas/webhooks/WebhookType.yaml | 2 ++ openapi/webhooks/external-account.yaml | 9 +++++++++ 5 files changed, 25 insertions(+) diff --git a/mintlify/openapi.yaml b/mintlify/openapi.yaml index 46d0c89d7..14a893762 100644 --- a/mintlify/openapi.yaml +++ b/mintlify/openapi.yaml @@ -11058,6 +11058,8 @@ webhooks: If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. ### Event types + - `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_NOT_REQUIRED` — Fired when an external account is created and ownership verification does not apply to it. The `data` payload contains the full external account object. + - `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_REQUIRED` — Fired when an external account requires ownership verification before transfers above regulatory thresholds can be sent to it. The `data` payload contains the full external account object. - `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_PENDING_REVIEW` — Fired when a submitted ownership verification enters review. The `data` payload contains the full external account object. - `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_VERIFIED` — Fired when ownership of the external account has been verified. The `data` payload contains the full external account object. - `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_FAILED` — Fired when an ownership verification attempt fails; a new verification can be started. The `data` payload contains the full external account object. @@ -25164,6 +25166,8 @@ components: - VERIFICATION.IN_PROGRESS - VERIFICATION.PENDING_MANUAL_REVIEW - VERIFICATION.READY_FOR_VERIFICATION + - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_NOT_REQUIRED + - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_REQUIRED - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_PENDING_REVIEW - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_VERIFIED - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_FAILED @@ -25373,6 +25377,8 @@ components: type: type: string enum: + - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_NOT_REQUIRED + - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_REQUIRED - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_PENDING_REVIEW - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_VERIFIED - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_FAILED diff --git a/openapi.yaml b/openapi.yaml index 46d0c89d7..14a893762 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -11058,6 +11058,8 @@ webhooks: If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. ### Event types + - `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_NOT_REQUIRED` — Fired when an external account is created and ownership verification does not apply to it. The `data` payload contains the full external account object. + - `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_REQUIRED` — Fired when an external account requires ownership verification before transfers above regulatory thresholds can be sent to it. The `data` payload contains the full external account object. - `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_PENDING_REVIEW` — Fired when a submitted ownership verification enters review. The `data` payload contains the full external account object. - `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_VERIFIED` — Fired when ownership of the external account has been verified. The `data` payload contains the full external account object. - `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_FAILED` — Fired when an ownership verification attempt fails; a new verification can be started. The `data` payload contains the full external account object. @@ -25164,6 +25166,8 @@ components: - VERIFICATION.IN_PROGRESS - VERIFICATION.PENDING_MANUAL_REVIEW - VERIFICATION.READY_FOR_VERIFICATION + - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_NOT_REQUIRED + - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_REQUIRED - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_PENDING_REVIEW - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_VERIFIED - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_FAILED @@ -25373,6 +25377,8 @@ components: type: type: string enum: + - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_NOT_REQUIRED + - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_REQUIRED - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_PENDING_REVIEW - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_VERIFIED - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_FAILED diff --git a/openapi/components/schemas/webhooks/ExternalAccountWebhook.yaml b/openapi/components/schemas/webhooks/ExternalAccountWebhook.yaml index 23006152b..136428934 100644 --- a/openapi/components/schemas/webhooks/ExternalAccountWebhook.yaml +++ b/openapi/components/schemas/webhooks/ExternalAccountWebhook.yaml @@ -9,6 +9,8 @@ allOf: type: type: string enum: + - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_NOT_REQUIRED + - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_REQUIRED - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_PENDING_REVIEW - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_VERIFIED - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_FAILED diff --git a/openapi/components/schemas/webhooks/WebhookType.yaml b/openapi/components/schemas/webhooks/WebhookType.yaml index b6ec84ded..80ad2f95d 100644 --- a/openapi/components/schemas/webhooks/WebhookType.yaml +++ b/openapi/components/schemas/webhooks/WebhookType.yaml @@ -26,6 +26,8 @@ enum: - VERIFICATION.IN_PROGRESS - VERIFICATION.PENDING_MANUAL_REVIEW - VERIFICATION.READY_FOR_VERIFICATION + - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_NOT_REQUIRED + - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_REQUIRED - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_PENDING_REVIEW - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_VERIFIED - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_FAILED diff --git a/openapi/webhooks/external-account.yaml b/openapi/webhooks/external-account.yaml index c969e5cf6..510251415 100644 --- a/openapi/webhooks/external-account.yaml +++ b/openapi/webhooks/external-account.yaml @@ -29,6 +29,15 @@ post: ### Event types + - `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_NOT_REQUIRED` — Fired when an + external account is created and ownership verification does not apply to + it. The `data` payload contains the full external account object. + + - `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_REQUIRED` — Fired when an + external account requires ownership verification before transfers above + regulatory thresholds can be sent to it. The `data` payload contains the + full external account object. + - `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_PENDING_REVIEW` — Fired when a submitted ownership verification enters review. The `data` payload contains the full external account object. From 7bb52cc17949c2858777d3413152f0a925ecb1a9 Mon Sep 17 00:00:00 2001 From: shreyav Date: Wed, 5 Aug 2026 22:44:46 -0700 Subject: [PATCH 3/4] Rework ownership verification into a first-class /ownership-verifications resource Replace the external-account verb endpoints (verify-ownership + verify-ownership/confirm under /customers and /platform) with a top-level Ownership Verifications API mirroring the KYC/KYB Verifications pattern: - POST /ownership-verifications, GET /ownership-verifications (filter by externalAccountId/status with cursor pagination), GET /ownership-verifications/{verificationId}, and POST /ownership-verifications/{verificationId}/confirm - OwnershipVerification is a oneOf on method: WalletSignatureOwnershipVerification (messageToSign, confirmed synchronously) and LivenessOwnershipVerification (verificationLink + token, completes asynchronously) - Drop ExternalAccount.ownershipVerificationStatus; add the PENDING_OWNERSHIP_VERIFICATION external-account status for FIRST_PARTY self-custody wallets on platforms subject to EU Travel Rule requirements - Replace EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_* webhooks with OWNERSHIP_VERIFICATION.{PENDING_REVIEW,VERIFIED,FAILED} resource webhooks and a new EXTERNAL_ACCOUNT.STATUS_UPDATED webhook - Trim removed error codes and extend AMOUNT_OUT_OF_RANGE description Co-Authored-By: Claude Fable 5 --- mintlify/openapi.yaml | 747 +++++++++++------- openapi.yaml | 747 +++++++++++------- .../components/schemas/errors/Error400.yaml | 8 +- .../external_accounts/ExternalAccount.yaml | 3 - .../ExternalAccountStatus.yaml | 11 +- .../OwnershipVerificationStart.yaml | 32 - .../OwnershipVerificationStartRequest.yaml | 10 - .../OwnershipVerificationStatus.yaml | 17 - .../LivenessOwnershipVerification.yaml | 59 ++ .../OwnershipVerification.yaml | 11 + .../OwnershipVerificationConfirmRequest.yaml | 3 +- .../OwnershipVerificationListResponse.yaml | 21 + .../OwnershipVerificationMethod.yaml | 0 .../OwnershipVerificationRequest.yaml | 17 + .../OwnershipVerificationState.yaml | 16 + .../WalletSignatureOwnershipVerification.yaml | 51 ++ .../ExternalAccountStatusWebhook.yaml | 12 + .../webhooks/ExternalAccountWebhook.yaml | 16 - .../OwnershipVerificationWebhook.yaml | 14 + .../schemas/webhooks/WebhookType.yaml | 9 +- openapi/openapi.yaml | 24 +- ..._{externalAccountId}_verify-ownership.yaml | 73 -- ...alAccountId}_verify-ownership_confirm.yaml | 70 -- .../ownership-verifications.yaml | 133 ++++ ...ership-verifications_{verificationId}.yaml | 40 + ...erifications_{verificationId}_confirm.yaml | 72 ++ ..._{externalAccountId}_verify-ownership.yaml | 73 -- ...alAccountId}_verify-ownership_confirm.yaml | 70 -- openapi/webhooks/external-account-status.yaml | 86 ++ openapi/webhooks/external-account.yaml | 119 --- openapi/webhooks/ownership-verification.yaml | 109 +++ 31 files changed, 1562 insertions(+), 1111 deletions(-) delete mode 100644 openapi/components/schemas/external_accounts/OwnershipVerificationStart.yaml delete mode 100644 openapi/components/schemas/external_accounts/OwnershipVerificationStartRequest.yaml delete mode 100644 openapi/components/schemas/external_accounts/OwnershipVerificationStatus.yaml create mode 100644 openapi/components/schemas/ownership_verifications/LivenessOwnershipVerification.yaml create mode 100644 openapi/components/schemas/ownership_verifications/OwnershipVerification.yaml rename openapi/components/schemas/{external_accounts => ownership_verifications}/OwnershipVerificationConfirmRequest.yaml (83%) create mode 100644 openapi/components/schemas/ownership_verifications/OwnershipVerificationListResponse.yaml rename openapi/components/schemas/{external_accounts => ownership_verifications}/OwnershipVerificationMethod.yaml (100%) create mode 100644 openapi/components/schemas/ownership_verifications/OwnershipVerificationRequest.yaml create mode 100644 openapi/components/schemas/ownership_verifications/OwnershipVerificationState.yaml create mode 100644 openapi/components/schemas/ownership_verifications/WalletSignatureOwnershipVerification.yaml create mode 100644 openapi/components/schemas/webhooks/ExternalAccountStatusWebhook.yaml delete mode 100644 openapi/components/schemas/webhooks/ExternalAccountWebhook.yaml create mode 100644 openapi/components/schemas/webhooks/OwnershipVerificationWebhook.yaml delete mode 100644 openapi/paths/customers/customers_external_accounts_{externalAccountId}_verify-ownership.yaml delete mode 100644 openapi/paths/customers/customers_external_accounts_{externalAccountId}_verify-ownership_confirm.yaml create mode 100644 openapi/paths/ownership_verifications/ownership-verifications.yaml create mode 100644 openapi/paths/ownership_verifications/ownership-verifications_{verificationId}.yaml create mode 100644 openapi/paths/ownership_verifications/ownership-verifications_{verificationId}_confirm.yaml delete mode 100644 openapi/paths/platform/platform_external_accounts_{externalAccountId}_verify-ownership.yaml delete mode 100644 openapi/paths/platform/platform_external_accounts_{externalAccountId}_verify-ownership_confirm.yaml create mode 100644 openapi/webhooks/external-account-status.yaml delete mode 100644 openapi/webhooks/external-account.yaml create mode 100644 openapi/webhooks/ownership-verification.yaml diff --git a/mintlify/openapi.yaml b/mintlify/openapi.yaml index 14a893762..d8ba938a3 100644 --- a/mintlify/openapi.yaml +++ b/mintlify/openapi.yaml @@ -33,6 +33,8 @@ tags: description: Internal account management endpoints for creating and managing internal accounts - name: External Accounts description: External account management endpoints for creating and managing external bank accounts + - name: Ownership Verifications + description: Endpoints for verifying ownership of self-custody crypto wallet external accounts, via wallet signature or a hosted biometric verification flow. - name: Same-Currency Transfers description: Endpoints for transferring funds between internal and external accounts with the same currency - name: Cross-Currency Transfers @@ -2429,151 +2431,6 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /customers/external-accounts/{externalAccountId}/verify-ownership: - parameters: - - name: externalAccountId - in: path - description: The unique identifier of the external account (self-custody crypto wallet) whose ownership is being verified. - required: true - schema: - type: string - post: - summary: Start external account ownership verification - description: | - Begin ownership verification for a `FIRST_PARTY` self-custody crypto wallet - external account. Choose a `method`: - - - `WALLET_SIGNATURE` — the response includes a `messageToSign`; have the - wallet sign it and submit the result to - `POST /customers/external-accounts/{externalAccountId}/verify-ownership/confirm` - to complete verification synchronously. - - `LIVENESS` — the response includes a `verificationLink` (and a `token` - for embedding); the user completes a hosted biometric flow and - verification completes asynchronously. Status transitions are delivered - via `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_*` webhooks and reflected in - the account's `ownershipVerificationStatus`. - - This endpoint is only meaningful for accounts whose - `ownershipVerificationStatus` is `REQUIRED` or `FAILED`. For other accounts, this returns `409`. - operationId: verifyExternalAccountOwnership - tags: - - External Accounts - security: - - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/OwnershipVerificationStartRequest' - responses: - '200': - description: Ownership verification started; the method-specific material is returned. - content: - application/json: - schema: - $ref: '#/components/schemas/OwnershipVerificationStart' - '400': - description: Invalid request - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '404': - description: Customer or external account not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '409': - description: Ownership verification is not applicable to this external account. - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - /customers/external-accounts/{externalAccountId}/verify-ownership/confirm: - parameters: - - name: externalAccountId - in: path - description: The unique identifier of the external account (self-custody crypto wallet) whose ownership is being verified. - required: true - schema: - type: string - post: - summary: Confirm external account ownership verification - description: | - Complete a `WALLET_SIGNATURE` ownership verification by submitting the - signature the wallet produced for the `messageToSign` returned by - `POST /customers/external-accounts/{externalAccountId}/verify-ownership`. - The message must be signed exactly as returned, and the signature must be - submitted before the session's `expiresAt`; after expiry, start a new - verification. - - Returns the updated external account, including its new - `ownershipVerificationStatus`. - - This endpoint is only meaningful for accounts with an in-progress - `WALLET_SIGNATURE` verification. For other accounts, this returns `409`. - operationId: confirmExternalAccountOwnershipVerification - tags: - - External Accounts - security: - - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/OwnershipVerificationConfirmRequest' - responses: - '200': - description: Ownership verification submitted; the updated external account is returned. - content: - application/json: - schema: - $ref: '#/components/schemas/ExternalAccount' - '400': - description: Invalid or expired signature - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '404': - description: Customer or external account not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '409': - description: Ownership verification is not applicable to this external account. - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' /platform/external-accounts: get: summary: List platform external accounts @@ -2778,35 +2635,30 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /platform/external-accounts/{externalAccountId}/verify-ownership: - parameters: - - name: externalAccountId - in: path - description: The unique identifier of the platform external account (self-custody crypto wallet) whose ownership is being verified. - required: true - schema: - type: string + /ownership-verifications: post: - summary: Start platform external account ownership verification + summary: Create an ownership verification description: | - Begin ownership verification for a `FIRST_PARTY` self-custody crypto wallet - external account owned by the platform. Choose a `method`: + Begin ownership verification for a `FIRST_PARTY` self-custody crypto + wallet external account (customer or platform owned). Choose a `method`: - `WALLET_SIGNATURE` — the response includes a `messageToSign`; have the wallet sign it and submit the result to - `POST /platform/external-accounts/{externalAccountId}/verify-ownership/confirm` - to complete verification synchronously. + `POST /ownership-verifications/{verificationId}/confirm` to complete + verification synchronously. - `LIVENESS` — the response includes a `verificationLink` (and a `token` for embedding); the user completes a hosted biometric flow and verification completes asynchronously. Status transitions are delivered - via `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_*` webhooks and reflected in - the account's `ownershipVerificationStatus`. + via `OWNERSHIP_VERIFICATION.*` webhooks or by polling + `GET /ownership-verifications/{verificationId}`. - This endpoint is only meaningful for accounts whose - `ownershipVerificationStatus` is `REQUIRED` or `FAILED`. For other accounts, this returns `409`. - operationId: verifyPlatformExternalAccountOwnership + Ownership verification applies to accounts in + `PENDING_OWNERSHIP_VERIFICATION` status; completing it moves the account + to `ACTIVE`. For accounts where ownership verification is not applicable, + this returns `409`. + operationId: createOwnershipVerification tags: - - External Accounts + - Ownership Verifications security: - BasicAuth: [] requestBody: @@ -2814,16 +2666,16 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/OwnershipVerificationStartRequest' + $ref: '#/components/schemas/OwnershipVerificationRequest' responses: - '200': - description: Ownership verification started; the method-specific material is returned. + '201': + description: Ownership verification created; the method-specific material is returned. content: application/json: schema: - $ref: '#/components/schemas/OwnershipVerificationStart' + $ref: '#/components/schemas/OwnershipVerification' '400': - description: Invalid request + description: Bad request - Invalid parameters content: application/json: schema: @@ -2852,34 +2704,136 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /platform/external-accounts/{externalAccountId}/verify-ownership/confirm: - parameters: - - name: externalAccountId - in: path - description: The unique identifier of the platform external account (self-custody crypto wallet) whose ownership is being verified. - required: true - schema: - type: string + get: + summary: List ownership verifications + description: | + Retrieve a list of ownership verifications with optional filtering by external account ID and status. + operationId: listOwnershipVerifications + tags: + - Ownership Verifications + security: + - BasicAuth: [] + parameters: + - name: externalAccountId + in: query + description: Filter by external account ID + required: false + schema: + type: string + - name: status + in: query + description: Filter by verification status + required: false + schema: + $ref: '#/components/schemas/OwnershipVerificationState' + - name: limit + in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor + in: query + description: Cursor for pagination (returned from previous request) + required: false + schema: + type: string + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/OwnershipVerificationListResponse' + '400': + description: Bad request - Invalid parameters + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /ownership-verifications/{verificationId}: + get: + summary: Get an ownership verification + description: Retrieve details of a specific ownership verification by ID. + operationId: getOwnershipVerification + tags: + - Ownership Verifications + security: + - BasicAuth: [] + parameters: + - name: verificationId + in: path + description: Ownership verification ID + required: true + schema: + type: string + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/OwnershipVerification' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: Ownership verification not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /ownership-verifications/{verificationId}/confirm: post: - summary: Confirm platform external account ownership verification + summary: Confirm an ownership verification description: | Complete a `WALLET_SIGNATURE` ownership verification by submitting the signature the wallet produced for the `messageToSign` returned by - `POST /platform/external-accounts/{externalAccountId}/verify-ownership`. - The message must be signed exactly as returned, and the signature must be - submitted before the session's `expiresAt`; after expiry, start a new - verification. - - Returns the updated external account, including its new - `ownershipVerificationStatus`. + `POST /ownership-verifications`. The message must be signed exactly as + returned, and the signature must be submitted before the session's + `expiresAt`; after expiry, start a new verification. - This endpoint is only meaningful for accounts with an in-progress - `WALLET_SIGNATURE` verification. For other accounts, this returns `409`. - operationId: confirmPlatformExternalAccountOwnershipVerification + This endpoint is only valid for `WALLET_SIGNATURE` verifications in + `PENDING` status. For other verifications, this returns `409`. `LIVENESS` + verifications complete asynchronously — their status is delivered via + `OWNERSHIP_VERIFICATION.*` webhooks or by polling + `GET /ownership-verifications/{verificationId}`. + operationId: confirmOwnershipVerification tags: - - External Accounts + - Ownership Verifications security: - BasicAuth: [] + parameters: + - name: verificationId + in: path + description: Ownership verification ID + required: true + schema: + type: string requestBody: required: true content: @@ -2888,11 +2842,11 @@ paths: $ref: '#/components/schemas/OwnershipVerificationConfirmRequest' responses: '200': - description: Ownership verification submitted; the updated external account is returned. + description: Signature submitted; the updated ownership verification is returned. content: application/json: schema: - $ref: '#/components/schemas/ExternalAccount' + $ref: '#/components/schemas/OwnershipVerification' '400': description: Invalid or expired signature content: @@ -2906,13 +2860,13 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: External account not found + description: Ownership verification not found content: application/json: schema: $ref: '#/components/schemas/Error404' '409': - description: Ownership verification is not applicable to this external account. + description: The verification is not a `WALLET_SIGNATURE` verification in `PENDING` status. content: application/json: schema: @@ -11040,11 +10994,11 @@ webhooks: application/json: schema: $ref: '#/components/schemas/Error409' - external-account: + external-account-status: post: - summary: External account ownership verification status change + summary: External account status webhook description: | - Webhook that is called when the ownership verification status of an external account changes. + Webhook that is called when the status of an external account changes (e.g., `PENDING_OWNERSHIP_VERIFICATION` → `ACTIVE` after ownership verification completes). This endpoint should be implemented by clients of the Grid API. ### Authentication @@ -11058,12 +11012,8 @@ webhooks: If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. ### Event types - - `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_NOT_REQUIRED` — Fired when an external account is created and ownership verification does not apply to it. The `data` payload contains the full external account object. - - `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_REQUIRED` — Fired when an external account requires ownership verification before transfers above regulatory thresholds can be sent to it. The `data` payload contains the full external account object. - - `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_PENDING_REVIEW` — Fired when a submitted ownership verification enters review. The `data` payload contains the full external account object. - - `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_VERIFIED` — Fired when ownership of the external account has been verified. The `data` payload contains the full external account object. - - `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_FAILED` — Fired when an ownership verification attempt fails; a new verification can be started. The `data` payload contains the full external account object. - operationId: externalAccountWebhook + - `EXTERNAL_ACCOUNT.STATUS_UPDATED` — Fired when the status of an external account changes. The `data` payload contains the full external account object. + operationId: externalAccountStatusWebhook tags: - Webhooks security: @@ -11073,29 +11023,13 @@ webhooks: content: application/json: schema: - $ref: '#/components/schemas/ExternalAccountWebhook' + $ref: '#/components/schemas/ExternalAccountStatusWebhook' examples: - ownershipVerified: - summary: Ownership of a self-custody wallet has been verified - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000040 - type: EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_VERIFIED - timestamp: '2025-08-15T14:32:00Z' - data: - id: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - customerId: Customer:da459a29-1fb7-41ce-a4cb-eb3a3c9fd7a7 - status: ACTIVE - currency: USDC - ownershipType: FIRST_PARTY - ownershipVerificationStatus: VERIFIED - accountInfo: - accountType: ETHEREUM_WALLET - address: '0xAbCDEF1234567890aBCdEf1234567890ABcDef12' - ownershipVerificationFailed: - summary: An ownership verification attempt failed + statusUpdated: + summary: A wallet account became active after ownership verification value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000041 - type: EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_FAILED + id: Webhook:019542f5-b3e7-1d02-0000-000000000042 + type: EXTERNAL_ACCOUNT.STATUS_UPDATED timestamp: '2025-08-15T14:32:00Z' data: id: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 @@ -11103,7 +11037,6 @@ webhooks: status: ACTIVE currency: USDC ownershipType: FIRST_PARTY - ownershipVerificationStatus: FAILED accountInfo: accountType: ETHEREUM_WALLET address: '0xAbCDEF1234567890aBCdEf1234567890ABcDef12' @@ -11208,6 +11141,92 @@ webhooks: application/json: schema: $ref: '#/components/schemas/Error409' + ownership-verification: + post: + summary: Ownership verification status change + description: | + Webhook that is called when the status of an ownership verification changes. + 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. + + ### Event types + - `OWNERSHIP_VERIFICATION.PENDING_REVIEW` — Fired when a submitted ownership verification enters review. The `data` payload contains the full ownership verification object. + - `OWNERSHIP_VERIFICATION.VERIFIED` — Fired when ownership of the external account has been verified. The `data` payload contains the full ownership verification object. + - `OWNERSHIP_VERIFICATION.FAILED` — Fired when an ownership verification attempt fails; start a new verification to retry. The `data` payload contains the full ownership verification object. + operationId: ownershipVerificationWebhook + tags: + - Webhooks + security: + - WebhookSignature: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/OwnershipVerificationWebhook' + examples: + verified: + summary: Ownership of a self-custody wallet has been verified + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000040 + type: OWNERSHIP_VERIFICATION.VERIFIED + timestamp: '2025-08-15T14:32:00Z' + data: + id: OwnershipVerification:019542f5-b3e7-1d02-0000-000000000001 + externalAccountId: ExternalAccount:019542f5-b3e7-1d02-0000-000000000001 + method: WALLET_SIGNATURE + status: VERIFIED + messageToSign: 'I verify that I control this wallet. Nonce: 019542f5-b3e7-1d02' + expiresAt: '2025-08-15T15:32:00Z' + createdAt: '2025-08-15T15:02:00Z' + updatedAt: '2025-08-15T14:32:00Z' + failed: + summary: An ownership verification attempt failed + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000041 + type: OWNERSHIP_VERIFICATION.FAILED + timestamp: '2025-08-15T14:32:00Z' + data: + id: OwnershipVerification:019542f5-b3e7-1d02-0000-000000000002 + externalAccountId: ExternalAccount:019542f5-b3e7-1d02-0000-000000000001 + method: LIVENESS + status: FAILED + verificationLink: https://verify.example.com/session/019542f5-b3e7-1d02 + token: eyJhbGciOiJIUzI1NiJ9.example + expiresAt: '2025-08-15T15:32:00Z' + createdAt: '2025-08-15T15:02:00Z' + updatedAt: '2025-08-15T14:32:00Z' + responses: + '200': + description: | + Webhook received successfully + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized - Signature validation failed + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '409': + description: Conflict - Webhook has already been processed (duplicate id) + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' card-state-change: post: summary: Card state change @@ -12193,7 +12212,7 @@ components: | INVALID_PUBKEY_FORMAT | Counterparty Public key format is invalid | | MISSING_REQUIRED_UMA_PARAMETERS | Counterparty required UMA parameters are missing | | SENDER_NOT_ACCEPTED | Sender is not accepted | - | AMOUNT_OUT_OF_RANGE | Amount is out of range | + | AMOUNT_OUT_OF_RANGE | Amount is out of range for the quote or the destination account | | INVALID_CURRENCY | Currency is invalid | | INVALID_TIMESTAMP | Timestamp is invalid | | INVALID_NONCE | Nonce is invalid | @@ -12217,10 +12236,7 @@ components: | STABLECOIN_PROVIDER_ACCOUNT_INVALID | The stablecoin provider account link is not usable | | STABLECOIN_PROVIDER_ACCOUNT_REVOKED | The stablecoin provider account link has been revoked | | STABLECOIN_PROVIDER_ACCOUNT_SELECTION_REQUIRED | Multiple active provider account links exist; pass `stablecoinProviderAccountId` to select one | - | OWNERSHIP_TYPE_REQUIRED | `ownershipType` must be provided for this external account | | WALLET_VERIFICATION_REQUIRED | The destination account's ownership must be verified before this transfer can proceed | - | THIRD_PARTY_TRANSFER_LIMIT_EXCEEDED | The transfer exceeds the permitted amount for third-party accounts | - | LIGHTNING_TRANSFER_LIMIT_EXCEEDED | The transfer exceeds the permitted amount for this destination type | enum: - INVALID_INPUT - END_USER_TERMS_VERSION_NOT_FOUND @@ -12262,10 +12278,7 @@ components: - STABLECOIN_PROVIDER_ACCOUNT_INVALID - STABLECOIN_PROVIDER_ACCOUNT_REVOKED - STABLECOIN_PROVIDER_ACCOUNT_SELECTION_REQUIRED - - OWNERSHIP_TYPE_REQUIRED - WALLET_VERIFICATION_REQUIRED - - THIRD_PARTY_TRANSFER_LIMIT_EXCEEDED - - LIGHTNING_TRANSFER_LIMIT_EXCEEDED message: type: string description: Error message @@ -17227,9 +17240,18 @@ components: enum: - PENDING - ACTIVE + - PENDING_OWNERSHIP_VERIFICATION - UNDER_REVIEW - INACTIVE - description: Status of an external account + description: | + Status of an external account. + + `PENDING_OWNERSHIP_VERIFICATION` applies to `FIRST_PARTY` self-custody + crypto wallet accounts on platforms subject to EU Travel Rule requirements. + While in this status, the account can be used for transfers below regulatory + thresholds; completing ownership verification (see the Ownership + Verifications API) moves the account to `ACTIVE` and removes the + restriction. OwnershipType: type: string enum: @@ -17264,24 +17286,6 @@ components: type: string description: The verified full name of the account holder as returned by the payment rail example: John Doe - OwnershipVerificationStatus: - type: string - enum: - - NOT_REQUIRED - - REQUIRED - - PENDING_REVIEW - - FAILED - - VERIFIED - description: | - The status of ownership verification for this external account. - - | Status | Description | - |--------|-------------| - | `NOT_REQUIRED` | Ownership verification does not apply to this account | - | `REQUIRED` | Ownership must be verified before transfers above regulatory thresholds can be sent to this account | - | `PENDING_REVIEW` | A verification was submitted and is under review | - | `FAILED` | The most recent verification attempt failed; a new verification can be started | - | `VERIFIED` | Ownership has been verified; no further action is needed | ExternalAccountType: type: string enum: @@ -19641,9 +19645,6 @@ components: beneficiaryVerifiedData: $ref: '#/components/schemas/BeneficiaryVerifiedData' description: Verified beneficiary data returned by the payment rail, if available - ownershipVerificationStatus: - $ref: '#/components/schemas/OwnershipVerificationStatus' - description: The status of ownership verification for this account accountInfo: $ref: '#/components/schemas/ExternalAccountInfoOneOf' ExternalAccountListResponse: @@ -20550,54 +20551,203 @@ components: default: false accountInfo: $ref: '#/components/schemas/ExternalAccountCreateInfoOneOf' - OwnershipVerificationMethod: + PlatformExternalAccountCreateRequest: + type: object + required: + - currency + - accountInfo + properties: + currency: + type: string + description: The ISO 4217 currency code + example: USD + platformAccountId: + type: string + description: Your platform's identifier for the account in your system. This can be used to reference the account by your own identifier. + example: ext_acc_123456 + ownershipType: + $ref: '#/components/schemas/OwnershipType' + accountInfo: + $ref: '#/components/schemas/ExternalAccountCreateInfoOneOf' + OwnershipVerificationState: type: string enum: - - WALLET_SIGNATURE - - LIVENESS + - PENDING + - PENDING_REVIEW + - VERIFIED + - FAILED description: | - The method used to verify ownership of a self-custody crypto wallet. + Current status of this ownership verification. - | Method | Description | + | Status | Description | |--------|-------------| - | `WALLET_SIGNATURE` | Prove control of the wallet by signing a message with the wallet's key | - | `LIVENESS` | Prove identity via a hosted biometric verification flow | - example: WALLET_SIGNATURE - OwnershipVerificationStartRequest: + | `PENDING` | Awaiting the wallet signature or the user completing the verification flow | + | `PENDING_REVIEW` | Submitted and under review | + | `VERIFIED` | Ownership was verified. Terminal | + | `FAILED` | This verification attempt failed. Terminal; start a new verification to retry | + example: PENDING + WalletSignatureOwnershipVerification: + title: Wallet Signature Ownership Verification type: object - description: Starts ownership verification for a self-custody crypto wallet external account. + description: An ownership verification completed by signing a message with the wallet's key. required: + - id + - externalAccountId - method + - status + - messageToSign + - expiresAt + - createdAt properties: + id: + type: string + description: Unique identifier for this ownership verification + example: OwnershipVerification:019542f5-b3e7-1d02-0000-000000000001 + externalAccountId: + type: string + description: The ID of the external account whose ownership is being verified + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000001 method: - $ref: '#/components/schemas/OwnershipVerificationMethod' - description: The verification method to use. - OwnershipVerificationStart: + type: string + enum: + - WALLET_SIGNATURE + description: The verification method. Always `WALLET_SIGNATURE` for this shape. + example: WALLET_SIGNATURE + status: + $ref: '#/components/schemas/OwnershipVerificationState' + messageToSign: + type: string + description: The exact message the wallet must sign, character-for-character. + example: 'I verify that I control this wallet. Nonce: 019542f5-b3e7-1d02' + expiresAt: + type: string + format: date-time + description: When this verification session expires. Prompt the user promptly; after expiry, a new verification must be started. + example: '2025-08-15T15:32:00Z' + createdAt: + type: string + format: date-time + description: When this verification was created + example: '2025-08-15T15:02:00Z' + updatedAt: + type: string + format: date-time + description: When this verification was last updated + example: '2025-08-15T15:02:00Z' + LivenessOwnershipVerification: + title: Liveness Ownership Verification type: object - description: 'The material needed to complete an ownership verification. Which fields are populated depends on the requested `method`: `messageToSign` for `WALLET_SIGNATURE`; `verificationLink` and `token` for `LIVENESS`.' + description: An ownership verification completed by the user through a hosted biometric verification flow. required: + - id + - externalAccountId + - method + - status + - verificationLink + - token - expiresAt + - createdAt properties: - messageToSign: + id: type: string - description: '`WALLET_SIGNATURE` only. The exact message the wallet must sign, character-for-character.' - example: 'I verify that I control this wallet. Nonce: 019542f5-b3e7-1d02' + description: Unique identifier for this ownership verification + example: OwnershipVerification:019542f5-b3e7-1d02-0000-000000000002 + externalAccountId: + type: string + description: The ID of the external account whose ownership is being verified + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000001 + method: + type: string + enum: + - LIVENESS + description: The verification method. Always `LIVENESS` for this shape. + example: LIVENESS + status: + $ref: '#/components/schemas/OwnershipVerificationState' verificationLink: type: string format: uri - description: '`LIVENESS` only. Hosted verification URL to present to the user.' + description: Hosted verification URL to present to the user. example: https://verify.example.com/session/019542f5-b3e7-1d02 token: type: string - description: '`LIVENESS` only. Access token for embedding the verification flow in the platform''s own UI, as an alternative to `verificationLink`.' + description: Access token for embedding the verification flow in the platform's own UI, as an alternative to `verificationLink`. + example: eyJhbGciOiJIUzI1NiJ9.example expiresAt: type: string format: date-time description: When this verification session expires. Prompt the user promptly; after expiry, a new verification must be started. example: '2025-08-15T15:32:00Z' + createdAt: + type: string + format: date-time + description: When this verification was created + example: '2025-08-15T15:02:00Z' + updatedAt: + type: string + format: date-time + description: When this verification was last updated + example: '2025-08-15T15:02:00Z' + OwnershipVerification: + description: An ownership verification for a self-custody crypto wallet external account. The shape is determined by the verification `method`. + oneOf: + - $ref: '#/components/schemas/WalletSignatureOwnershipVerification' + - $ref: '#/components/schemas/LivenessOwnershipVerification' + discriminator: + propertyName: method + mapping: + WALLET_SIGNATURE: '#/components/schemas/WalletSignatureOwnershipVerification' + LIVENESS: '#/components/schemas/LivenessOwnershipVerification' + OwnershipVerificationListResponse: + type: object + required: + - data + - hasMore + properties: + data: + type: array + description: List of ownership verifications matching the filter criteria + items: + $ref: '#/components/schemas/OwnershipVerification' + hasMore: + type: boolean + description: Indicates if more results are available beyond this page + nextCursor: + type: string + description: Cursor to retrieve the next page of results (only present if hasMore is true) + totalCount: + type: integer + description: Total number of results matching the criteria + OwnershipVerificationMethod: + type: string + enum: + - WALLET_SIGNATURE + - LIVENESS + description: | + The method used to verify ownership of a self-custody crypto wallet. + + | Method | Description | + |--------|-------------| + | `WALLET_SIGNATURE` | Prove control of the wallet by signing a message with the wallet's key | + | `LIVENESS` | Prove identity via a hosted biometric verification flow | + example: WALLET_SIGNATURE + OwnershipVerificationRequest: + type: object + description: Creates an ownership verification for a self-custody crypto wallet external account. + required: + - externalAccountId + - method + properties: + externalAccountId: + type: string + description: The ID of the external account (self-custody crypto wallet) whose ownership is being verified. + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000001 + method: + $ref: '#/components/schemas/OwnershipVerificationMethod' + description: The verification method to use. OwnershipVerificationConfirmRequest: type: object - description: Completes a `WALLET_SIGNATURE` ownership verification by submitting the signature the wallet produced for the `messageToSign` from the start step. + description: Completes a `WALLET_SIGNATURE` ownership verification by submitting the signature the wallet produced for the verification's `messageToSign`. required: - signature - signedAddress @@ -20605,6 +20755,7 @@ components: signature: type: string description: The signature produced over the exact `messageToSign` — EIP-191 hex for EVM chains, base64 for Bitcoin, base58-encoded Ed25519 for Solana. + example: '0x52d75f01c9e7b8b2ce2fbcbd21bfeeee7bcd1a2f01ce6b8ad9a67a45e83a8f5d1c' signedAddress: type: string description: The wallet address that signed the message. @@ -20616,24 +20767,6 @@ components: - electrum default: bip137 description: Bitcoin message-signing format. Defaults to `bip137`; use `electrum` for Electrum/Sparrow wallets. Ignored for non-Bitcoin chains. - PlatformExternalAccountCreateRequest: - type: object - required: - - currency - - accountInfo - properties: - currency: - type: string - description: The ISO 4217 currency code - example: USD - platformAccountId: - type: string - description: Your platform's identifier for the account in your system. This can be used to reference the account by your own identifier. - example: ext_acc_123456 - ownershipType: - $ref: '#/components/schemas/OwnershipType' - accountInfo: - $ref: '#/components/schemas/ExternalAccountCreateInfoOneOf' BeneficialOwnerListResponse: type: object required: @@ -25166,11 +25299,10 @@ components: - VERIFICATION.IN_PROGRESS - VERIFICATION.PENDING_MANUAL_REVIEW - VERIFICATION.READY_FOR_VERIFICATION - - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_NOT_REQUIRED - - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_REQUIRED - - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_PENDING_REVIEW - - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_VERIFIED - - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_FAILED + - OWNERSHIP_VERIFICATION.PENDING_REVIEW + - OWNERSHIP_VERIFICATION.VERIFIED + - OWNERSHIP_VERIFICATION.FAILED + - EXTERNAL_ACCOUNT.STATUS_UPDATED - INTERNAL_ACCOUNT.BALANCE_UPDATED - INTERNAL_ACCOUNT.STATUS_UPDATED - INVITATION.CLAIMED @@ -25365,7 +25497,7 @@ components: enum: - INTERNAL_ACCOUNT.BALANCE_UPDATED - INTERNAL_ACCOUNT.STATUS_UPDATED - ExternalAccountWebhook: + ExternalAccountStatusWebhook: allOf: - $ref: '#/components/schemas/BaseWebhook' - type: object @@ -25377,11 +25509,7 @@ components: type: type: string enum: - - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_NOT_REQUIRED - - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_REQUIRED - - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_PENDING_REVIEW - - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_VERIFIED - - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_FAILED + - EXTERNAL_ACCOUNT.STATUS_UPDATED VerificationWebhook: allOf: - $ref: '#/components/schemas/BaseWebhook' @@ -25399,6 +25527,21 @@ components: - VERIFICATION.RESOLVE_ERRORS - VERIFICATION.IN_PROGRESS - VERIFICATION.PENDING_MANUAL_REVIEW + OwnershipVerificationWebhook: + allOf: + - $ref: '#/components/schemas/BaseWebhook' + - type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/OwnershipVerification' + type: + type: string + enum: + - OWNERSHIP_VERIFICATION.PENDING_REVIEW + - OWNERSHIP_VERIFICATION.VERIFIED + - OWNERSHIP_VERIFICATION.FAILED CardStateChangeWebhook: allOf: - $ref: '#/components/schemas/BaseWebhook' diff --git a/openapi.yaml b/openapi.yaml index 14a893762..d8ba938a3 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -33,6 +33,8 @@ tags: description: Internal account management endpoints for creating and managing internal accounts - name: External Accounts description: External account management endpoints for creating and managing external bank accounts + - name: Ownership Verifications + description: Endpoints for verifying ownership of self-custody crypto wallet external accounts, via wallet signature or a hosted biometric verification flow. - name: Same-Currency Transfers description: Endpoints for transferring funds between internal and external accounts with the same currency - name: Cross-Currency Transfers @@ -2429,151 +2431,6 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /customers/external-accounts/{externalAccountId}/verify-ownership: - parameters: - - name: externalAccountId - in: path - description: The unique identifier of the external account (self-custody crypto wallet) whose ownership is being verified. - required: true - schema: - type: string - post: - summary: Start external account ownership verification - description: | - Begin ownership verification for a `FIRST_PARTY` self-custody crypto wallet - external account. Choose a `method`: - - - `WALLET_SIGNATURE` — the response includes a `messageToSign`; have the - wallet sign it and submit the result to - `POST /customers/external-accounts/{externalAccountId}/verify-ownership/confirm` - to complete verification synchronously. - - `LIVENESS` — the response includes a `verificationLink` (and a `token` - for embedding); the user completes a hosted biometric flow and - verification completes asynchronously. Status transitions are delivered - via `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_*` webhooks and reflected in - the account's `ownershipVerificationStatus`. - - This endpoint is only meaningful for accounts whose - `ownershipVerificationStatus` is `REQUIRED` or `FAILED`. For other accounts, this returns `409`. - operationId: verifyExternalAccountOwnership - tags: - - External Accounts - security: - - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/OwnershipVerificationStartRequest' - responses: - '200': - description: Ownership verification started; the method-specific material is returned. - content: - application/json: - schema: - $ref: '#/components/schemas/OwnershipVerificationStart' - '400': - description: Invalid request - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '404': - description: Customer or external account not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '409': - description: Ownership verification is not applicable to this external account. - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' - /customers/external-accounts/{externalAccountId}/verify-ownership/confirm: - parameters: - - name: externalAccountId - in: path - description: The unique identifier of the external account (self-custody crypto wallet) whose ownership is being verified. - required: true - schema: - type: string - post: - summary: Confirm external account ownership verification - description: | - Complete a `WALLET_SIGNATURE` ownership verification by submitting the - signature the wallet produced for the `messageToSign` returned by - `POST /customers/external-accounts/{externalAccountId}/verify-ownership`. - The message must be signed exactly as returned, and the signature must be - submitted before the session's `expiresAt`; after expiry, start a new - verification. - - Returns the updated external account, including its new - `ownershipVerificationStatus`. - - This endpoint is only meaningful for accounts with an in-progress - `WALLET_SIGNATURE` verification. For other accounts, this returns `409`. - operationId: confirmExternalAccountOwnershipVerification - tags: - - External Accounts - security: - - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/OwnershipVerificationConfirmRequest' - responses: - '200': - description: Ownership verification submitted; the updated external account is returned. - content: - application/json: - schema: - $ref: '#/components/schemas/ExternalAccount' - '400': - description: Invalid or expired signature - content: - application/json: - schema: - $ref: '#/components/schemas/Error400' - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error401' - '404': - description: Customer or external account not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error404' - '409': - description: Ownership verification is not applicable to this external account. - content: - application/json: - schema: - $ref: '#/components/schemas/Error409' - '500': - description: Internal service error - content: - application/json: - schema: - $ref: '#/components/schemas/Error500' /platform/external-accounts: get: summary: List platform external accounts @@ -2778,35 +2635,30 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /platform/external-accounts/{externalAccountId}/verify-ownership: - parameters: - - name: externalAccountId - in: path - description: The unique identifier of the platform external account (self-custody crypto wallet) whose ownership is being verified. - required: true - schema: - type: string + /ownership-verifications: post: - summary: Start platform external account ownership verification + summary: Create an ownership verification description: | - Begin ownership verification for a `FIRST_PARTY` self-custody crypto wallet - external account owned by the platform. Choose a `method`: + Begin ownership verification for a `FIRST_PARTY` self-custody crypto + wallet external account (customer or platform owned). Choose a `method`: - `WALLET_SIGNATURE` — the response includes a `messageToSign`; have the wallet sign it and submit the result to - `POST /platform/external-accounts/{externalAccountId}/verify-ownership/confirm` - to complete verification synchronously. + `POST /ownership-verifications/{verificationId}/confirm` to complete + verification synchronously. - `LIVENESS` — the response includes a `verificationLink` (and a `token` for embedding); the user completes a hosted biometric flow and verification completes asynchronously. Status transitions are delivered - via `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_*` webhooks and reflected in - the account's `ownershipVerificationStatus`. + via `OWNERSHIP_VERIFICATION.*` webhooks or by polling + `GET /ownership-verifications/{verificationId}`. - This endpoint is only meaningful for accounts whose - `ownershipVerificationStatus` is `REQUIRED` or `FAILED`. For other accounts, this returns `409`. - operationId: verifyPlatformExternalAccountOwnership + Ownership verification applies to accounts in + `PENDING_OWNERSHIP_VERIFICATION` status; completing it moves the account + to `ACTIVE`. For accounts where ownership verification is not applicable, + this returns `409`. + operationId: createOwnershipVerification tags: - - External Accounts + - Ownership Verifications security: - BasicAuth: [] requestBody: @@ -2814,16 +2666,16 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/OwnershipVerificationStartRequest' + $ref: '#/components/schemas/OwnershipVerificationRequest' responses: - '200': - description: Ownership verification started; the method-specific material is returned. + '201': + description: Ownership verification created; the method-specific material is returned. content: application/json: schema: - $ref: '#/components/schemas/OwnershipVerificationStart' + $ref: '#/components/schemas/OwnershipVerification' '400': - description: Invalid request + description: Bad request - Invalid parameters content: application/json: schema: @@ -2852,34 +2704,136 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /platform/external-accounts/{externalAccountId}/verify-ownership/confirm: - parameters: - - name: externalAccountId - in: path - description: The unique identifier of the platform external account (self-custody crypto wallet) whose ownership is being verified. - required: true - schema: - type: string + get: + summary: List ownership verifications + description: | + Retrieve a list of ownership verifications with optional filtering by external account ID and status. + operationId: listOwnershipVerifications + tags: + - Ownership Verifications + security: + - BasicAuth: [] + parameters: + - name: externalAccountId + in: query + description: Filter by external account ID + required: false + schema: + type: string + - name: status + in: query + description: Filter by verification status + required: false + schema: + $ref: '#/components/schemas/OwnershipVerificationState' + - name: limit + in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor + in: query + description: Cursor for pagination (returned from previous request) + required: false + schema: + type: string + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/OwnershipVerificationListResponse' + '400': + description: Bad request - Invalid parameters + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /ownership-verifications/{verificationId}: + get: + summary: Get an ownership verification + description: Retrieve details of a specific ownership verification by ID. + operationId: getOwnershipVerification + tags: + - Ownership Verifications + security: + - BasicAuth: [] + parameters: + - name: verificationId + in: path + description: Ownership verification ID + required: true + schema: + type: string + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/OwnershipVerification' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: Ownership verification not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + /ownership-verifications/{verificationId}/confirm: post: - summary: Confirm platform external account ownership verification + summary: Confirm an ownership verification description: | Complete a `WALLET_SIGNATURE` ownership verification by submitting the signature the wallet produced for the `messageToSign` returned by - `POST /platform/external-accounts/{externalAccountId}/verify-ownership`. - The message must be signed exactly as returned, and the signature must be - submitted before the session's `expiresAt`; after expiry, start a new - verification. - - Returns the updated external account, including its new - `ownershipVerificationStatus`. + `POST /ownership-verifications`. The message must be signed exactly as + returned, and the signature must be submitted before the session's + `expiresAt`; after expiry, start a new verification. - This endpoint is only meaningful for accounts with an in-progress - `WALLET_SIGNATURE` verification. For other accounts, this returns `409`. - operationId: confirmPlatformExternalAccountOwnershipVerification + This endpoint is only valid for `WALLET_SIGNATURE` verifications in + `PENDING` status. For other verifications, this returns `409`. `LIVENESS` + verifications complete asynchronously — their status is delivered via + `OWNERSHIP_VERIFICATION.*` webhooks or by polling + `GET /ownership-verifications/{verificationId}`. + operationId: confirmOwnershipVerification tags: - - External Accounts + - Ownership Verifications security: - BasicAuth: [] + parameters: + - name: verificationId + in: path + description: Ownership verification ID + required: true + schema: + type: string requestBody: required: true content: @@ -2888,11 +2842,11 @@ paths: $ref: '#/components/schemas/OwnershipVerificationConfirmRequest' responses: '200': - description: Ownership verification submitted; the updated external account is returned. + description: Signature submitted; the updated ownership verification is returned. content: application/json: schema: - $ref: '#/components/schemas/ExternalAccount' + $ref: '#/components/schemas/OwnershipVerification' '400': description: Invalid or expired signature content: @@ -2906,13 +2860,13 @@ paths: schema: $ref: '#/components/schemas/Error401' '404': - description: External account not found + description: Ownership verification not found content: application/json: schema: $ref: '#/components/schemas/Error404' '409': - description: Ownership verification is not applicable to this external account. + description: The verification is not a `WALLET_SIGNATURE` verification in `PENDING` status. content: application/json: schema: @@ -11040,11 +10994,11 @@ webhooks: application/json: schema: $ref: '#/components/schemas/Error409' - external-account: + external-account-status: post: - summary: External account ownership verification status change + summary: External account status webhook description: | - Webhook that is called when the ownership verification status of an external account changes. + Webhook that is called when the status of an external account changes (e.g., `PENDING_OWNERSHIP_VERIFICATION` → `ACTIVE` after ownership verification completes). This endpoint should be implemented by clients of the Grid API. ### Authentication @@ -11058,12 +11012,8 @@ webhooks: If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. ### Event types - - `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_NOT_REQUIRED` — Fired when an external account is created and ownership verification does not apply to it. The `data` payload contains the full external account object. - - `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_REQUIRED` — Fired when an external account requires ownership verification before transfers above regulatory thresholds can be sent to it. The `data` payload contains the full external account object. - - `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_PENDING_REVIEW` — Fired when a submitted ownership verification enters review. The `data` payload contains the full external account object. - - `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_VERIFIED` — Fired when ownership of the external account has been verified. The `data` payload contains the full external account object. - - `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_FAILED` — Fired when an ownership verification attempt fails; a new verification can be started. The `data` payload contains the full external account object. - operationId: externalAccountWebhook + - `EXTERNAL_ACCOUNT.STATUS_UPDATED` — Fired when the status of an external account changes. The `data` payload contains the full external account object. + operationId: externalAccountStatusWebhook tags: - Webhooks security: @@ -11073,29 +11023,13 @@ webhooks: content: application/json: schema: - $ref: '#/components/schemas/ExternalAccountWebhook' + $ref: '#/components/schemas/ExternalAccountStatusWebhook' examples: - ownershipVerified: - summary: Ownership of a self-custody wallet has been verified - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000040 - type: EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_VERIFIED - timestamp: '2025-08-15T14:32:00Z' - data: - id: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - customerId: Customer:da459a29-1fb7-41ce-a4cb-eb3a3c9fd7a7 - status: ACTIVE - currency: USDC - ownershipType: FIRST_PARTY - ownershipVerificationStatus: VERIFIED - accountInfo: - accountType: ETHEREUM_WALLET - address: '0xAbCDEF1234567890aBCdEf1234567890ABcDef12' - ownershipVerificationFailed: - summary: An ownership verification attempt failed + statusUpdated: + summary: A wallet account became active after ownership verification value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000041 - type: EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_FAILED + id: Webhook:019542f5-b3e7-1d02-0000-000000000042 + type: EXTERNAL_ACCOUNT.STATUS_UPDATED timestamp: '2025-08-15T14:32:00Z' data: id: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 @@ -11103,7 +11037,6 @@ webhooks: status: ACTIVE currency: USDC ownershipType: FIRST_PARTY - ownershipVerificationStatus: FAILED accountInfo: accountType: ETHEREUM_WALLET address: '0xAbCDEF1234567890aBCdEf1234567890ABcDef12' @@ -11208,6 +11141,92 @@ webhooks: application/json: schema: $ref: '#/components/schemas/Error409' + ownership-verification: + post: + summary: Ownership verification status change + description: | + Webhook that is called when the status of an ownership verification changes. + 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. + + ### Event types + - `OWNERSHIP_VERIFICATION.PENDING_REVIEW` — Fired when a submitted ownership verification enters review. The `data` payload contains the full ownership verification object. + - `OWNERSHIP_VERIFICATION.VERIFIED` — Fired when ownership of the external account has been verified. The `data` payload contains the full ownership verification object. + - `OWNERSHIP_VERIFICATION.FAILED` — Fired when an ownership verification attempt fails; start a new verification to retry. The `data` payload contains the full ownership verification object. + operationId: ownershipVerificationWebhook + tags: + - Webhooks + security: + - WebhookSignature: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/OwnershipVerificationWebhook' + examples: + verified: + summary: Ownership of a self-custody wallet has been verified + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000040 + type: OWNERSHIP_VERIFICATION.VERIFIED + timestamp: '2025-08-15T14:32:00Z' + data: + id: OwnershipVerification:019542f5-b3e7-1d02-0000-000000000001 + externalAccountId: ExternalAccount:019542f5-b3e7-1d02-0000-000000000001 + method: WALLET_SIGNATURE + status: VERIFIED + messageToSign: 'I verify that I control this wallet. Nonce: 019542f5-b3e7-1d02' + expiresAt: '2025-08-15T15:32:00Z' + createdAt: '2025-08-15T15:02:00Z' + updatedAt: '2025-08-15T14:32:00Z' + failed: + summary: An ownership verification attempt failed + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000041 + type: OWNERSHIP_VERIFICATION.FAILED + timestamp: '2025-08-15T14:32:00Z' + data: + id: OwnershipVerification:019542f5-b3e7-1d02-0000-000000000002 + externalAccountId: ExternalAccount:019542f5-b3e7-1d02-0000-000000000001 + method: LIVENESS + status: FAILED + verificationLink: https://verify.example.com/session/019542f5-b3e7-1d02 + token: eyJhbGciOiJIUzI1NiJ9.example + expiresAt: '2025-08-15T15:32:00Z' + createdAt: '2025-08-15T15:02:00Z' + updatedAt: '2025-08-15T14:32:00Z' + responses: + '200': + description: | + Webhook received successfully + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized - Signature validation failed + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '409': + description: Conflict - Webhook has already been processed (duplicate id) + content: + application/json: + schema: + $ref: '#/components/schemas/Error409' card-state-change: post: summary: Card state change @@ -12193,7 +12212,7 @@ components: | INVALID_PUBKEY_FORMAT | Counterparty Public key format is invalid | | MISSING_REQUIRED_UMA_PARAMETERS | Counterparty required UMA parameters are missing | | SENDER_NOT_ACCEPTED | Sender is not accepted | - | AMOUNT_OUT_OF_RANGE | Amount is out of range | + | AMOUNT_OUT_OF_RANGE | Amount is out of range for the quote or the destination account | | INVALID_CURRENCY | Currency is invalid | | INVALID_TIMESTAMP | Timestamp is invalid | | INVALID_NONCE | Nonce is invalid | @@ -12217,10 +12236,7 @@ components: | STABLECOIN_PROVIDER_ACCOUNT_INVALID | The stablecoin provider account link is not usable | | STABLECOIN_PROVIDER_ACCOUNT_REVOKED | The stablecoin provider account link has been revoked | | STABLECOIN_PROVIDER_ACCOUNT_SELECTION_REQUIRED | Multiple active provider account links exist; pass `stablecoinProviderAccountId` to select one | - | OWNERSHIP_TYPE_REQUIRED | `ownershipType` must be provided for this external account | | WALLET_VERIFICATION_REQUIRED | The destination account's ownership must be verified before this transfer can proceed | - | THIRD_PARTY_TRANSFER_LIMIT_EXCEEDED | The transfer exceeds the permitted amount for third-party accounts | - | LIGHTNING_TRANSFER_LIMIT_EXCEEDED | The transfer exceeds the permitted amount for this destination type | enum: - INVALID_INPUT - END_USER_TERMS_VERSION_NOT_FOUND @@ -12262,10 +12278,7 @@ components: - STABLECOIN_PROVIDER_ACCOUNT_INVALID - STABLECOIN_PROVIDER_ACCOUNT_REVOKED - STABLECOIN_PROVIDER_ACCOUNT_SELECTION_REQUIRED - - OWNERSHIP_TYPE_REQUIRED - WALLET_VERIFICATION_REQUIRED - - THIRD_PARTY_TRANSFER_LIMIT_EXCEEDED - - LIGHTNING_TRANSFER_LIMIT_EXCEEDED message: type: string description: Error message @@ -17227,9 +17240,18 @@ components: enum: - PENDING - ACTIVE + - PENDING_OWNERSHIP_VERIFICATION - UNDER_REVIEW - INACTIVE - description: Status of an external account + description: | + Status of an external account. + + `PENDING_OWNERSHIP_VERIFICATION` applies to `FIRST_PARTY` self-custody + crypto wallet accounts on platforms subject to EU Travel Rule requirements. + While in this status, the account can be used for transfers below regulatory + thresholds; completing ownership verification (see the Ownership + Verifications API) moves the account to `ACTIVE` and removes the + restriction. OwnershipType: type: string enum: @@ -17264,24 +17286,6 @@ components: type: string description: The verified full name of the account holder as returned by the payment rail example: John Doe - OwnershipVerificationStatus: - type: string - enum: - - NOT_REQUIRED - - REQUIRED - - PENDING_REVIEW - - FAILED - - VERIFIED - description: | - The status of ownership verification for this external account. - - | Status | Description | - |--------|-------------| - | `NOT_REQUIRED` | Ownership verification does not apply to this account | - | `REQUIRED` | Ownership must be verified before transfers above regulatory thresholds can be sent to this account | - | `PENDING_REVIEW` | A verification was submitted and is under review | - | `FAILED` | The most recent verification attempt failed; a new verification can be started | - | `VERIFIED` | Ownership has been verified; no further action is needed | ExternalAccountType: type: string enum: @@ -19641,9 +19645,6 @@ components: beneficiaryVerifiedData: $ref: '#/components/schemas/BeneficiaryVerifiedData' description: Verified beneficiary data returned by the payment rail, if available - ownershipVerificationStatus: - $ref: '#/components/schemas/OwnershipVerificationStatus' - description: The status of ownership verification for this account accountInfo: $ref: '#/components/schemas/ExternalAccountInfoOneOf' ExternalAccountListResponse: @@ -20550,54 +20551,203 @@ components: default: false accountInfo: $ref: '#/components/schemas/ExternalAccountCreateInfoOneOf' - OwnershipVerificationMethod: + PlatformExternalAccountCreateRequest: + type: object + required: + - currency + - accountInfo + properties: + currency: + type: string + description: The ISO 4217 currency code + example: USD + platformAccountId: + type: string + description: Your platform's identifier for the account in your system. This can be used to reference the account by your own identifier. + example: ext_acc_123456 + ownershipType: + $ref: '#/components/schemas/OwnershipType' + accountInfo: + $ref: '#/components/schemas/ExternalAccountCreateInfoOneOf' + OwnershipVerificationState: type: string enum: - - WALLET_SIGNATURE - - LIVENESS + - PENDING + - PENDING_REVIEW + - VERIFIED + - FAILED description: | - The method used to verify ownership of a self-custody crypto wallet. + Current status of this ownership verification. - | Method | Description | + | Status | Description | |--------|-------------| - | `WALLET_SIGNATURE` | Prove control of the wallet by signing a message with the wallet's key | - | `LIVENESS` | Prove identity via a hosted biometric verification flow | - example: WALLET_SIGNATURE - OwnershipVerificationStartRequest: + | `PENDING` | Awaiting the wallet signature or the user completing the verification flow | + | `PENDING_REVIEW` | Submitted and under review | + | `VERIFIED` | Ownership was verified. Terminal | + | `FAILED` | This verification attempt failed. Terminal; start a new verification to retry | + example: PENDING + WalletSignatureOwnershipVerification: + title: Wallet Signature Ownership Verification type: object - description: Starts ownership verification for a self-custody crypto wallet external account. + description: An ownership verification completed by signing a message with the wallet's key. required: + - id + - externalAccountId - method + - status + - messageToSign + - expiresAt + - createdAt properties: + id: + type: string + description: Unique identifier for this ownership verification + example: OwnershipVerification:019542f5-b3e7-1d02-0000-000000000001 + externalAccountId: + type: string + description: The ID of the external account whose ownership is being verified + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000001 method: - $ref: '#/components/schemas/OwnershipVerificationMethod' - description: The verification method to use. - OwnershipVerificationStart: + type: string + enum: + - WALLET_SIGNATURE + description: The verification method. Always `WALLET_SIGNATURE` for this shape. + example: WALLET_SIGNATURE + status: + $ref: '#/components/schemas/OwnershipVerificationState' + messageToSign: + type: string + description: The exact message the wallet must sign, character-for-character. + example: 'I verify that I control this wallet. Nonce: 019542f5-b3e7-1d02' + expiresAt: + type: string + format: date-time + description: When this verification session expires. Prompt the user promptly; after expiry, a new verification must be started. + example: '2025-08-15T15:32:00Z' + createdAt: + type: string + format: date-time + description: When this verification was created + example: '2025-08-15T15:02:00Z' + updatedAt: + type: string + format: date-time + description: When this verification was last updated + example: '2025-08-15T15:02:00Z' + LivenessOwnershipVerification: + title: Liveness Ownership Verification type: object - description: 'The material needed to complete an ownership verification. Which fields are populated depends on the requested `method`: `messageToSign` for `WALLET_SIGNATURE`; `verificationLink` and `token` for `LIVENESS`.' + description: An ownership verification completed by the user through a hosted biometric verification flow. required: + - id + - externalAccountId + - method + - status + - verificationLink + - token - expiresAt + - createdAt properties: - messageToSign: + id: type: string - description: '`WALLET_SIGNATURE` only. The exact message the wallet must sign, character-for-character.' - example: 'I verify that I control this wallet. Nonce: 019542f5-b3e7-1d02' + description: Unique identifier for this ownership verification + example: OwnershipVerification:019542f5-b3e7-1d02-0000-000000000002 + externalAccountId: + type: string + description: The ID of the external account whose ownership is being verified + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000001 + method: + type: string + enum: + - LIVENESS + description: The verification method. Always `LIVENESS` for this shape. + example: LIVENESS + status: + $ref: '#/components/schemas/OwnershipVerificationState' verificationLink: type: string format: uri - description: '`LIVENESS` only. Hosted verification URL to present to the user.' + description: Hosted verification URL to present to the user. example: https://verify.example.com/session/019542f5-b3e7-1d02 token: type: string - description: '`LIVENESS` only. Access token for embedding the verification flow in the platform''s own UI, as an alternative to `verificationLink`.' + description: Access token for embedding the verification flow in the platform's own UI, as an alternative to `verificationLink`. + example: eyJhbGciOiJIUzI1NiJ9.example expiresAt: type: string format: date-time description: When this verification session expires. Prompt the user promptly; after expiry, a new verification must be started. example: '2025-08-15T15:32:00Z' + createdAt: + type: string + format: date-time + description: When this verification was created + example: '2025-08-15T15:02:00Z' + updatedAt: + type: string + format: date-time + description: When this verification was last updated + example: '2025-08-15T15:02:00Z' + OwnershipVerification: + description: An ownership verification for a self-custody crypto wallet external account. The shape is determined by the verification `method`. + oneOf: + - $ref: '#/components/schemas/WalletSignatureOwnershipVerification' + - $ref: '#/components/schemas/LivenessOwnershipVerification' + discriminator: + propertyName: method + mapping: + WALLET_SIGNATURE: '#/components/schemas/WalletSignatureOwnershipVerification' + LIVENESS: '#/components/schemas/LivenessOwnershipVerification' + OwnershipVerificationListResponse: + type: object + required: + - data + - hasMore + properties: + data: + type: array + description: List of ownership verifications matching the filter criteria + items: + $ref: '#/components/schemas/OwnershipVerification' + hasMore: + type: boolean + description: Indicates if more results are available beyond this page + nextCursor: + type: string + description: Cursor to retrieve the next page of results (only present if hasMore is true) + totalCount: + type: integer + description: Total number of results matching the criteria + OwnershipVerificationMethod: + type: string + enum: + - WALLET_SIGNATURE + - LIVENESS + description: | + The method used to verify ownership of a self-custody crypto wallet. + + | Method | Description | + |--------|-------------| + | `WALLET_SIGNATURE` | Prove control of the wallet by signing a message with the wallet's key | + | `LIVENESS` | Prove identity via a hosted biometric verification flow | + example: WALLET_SIGNATURE + OwnershipVerificationRequest: + type: object + description: Creates an ownership verification for a self-custody crypto wallet external account. + required: + - externalAccountId + - method + properties: + externalAccountId: + type: string + description: The ID of the external account (self-custody crypto wallet) whose ownership is being verified. + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000001 + method: + $ref: '#/components/schemas/OwnershipVerificationMethod' + description: The verification method to use. OwnershipVerificationConfirmRequest: type: object - description: Completes a `WALLET_SIGNATURE` ownership verification by submitting the signature the wallet produced for the `messageToSign` from the start step. + description: Completes a `WALLET_SIGNATURE` ownership verification by submitting the signature the wallet produced for the verification's `messageToSign`. required: - signature - signedAddress @@ -20605,6 +20755,7 @@ components: signature: type: string description: The signature produced over the exact `messageToSign` — EIP-191 hex for EVM chains, base64 for Bitcoin, base58-encoded Ed25519 for Solana. + example: '0x52d75f01c9e7b8b2ce2fbcbd21bfeeee7bcd1a2f01ce6b8ad9a67a45e83a8f5d1c' signedAddress: type: string description: The wallet address that signed the message. @@ -20616,24 +20767,6 @@ components: - electrum default: bip137 description: Bitcoin message-signing format. Defaults to `bip137`; use `electrum` for Electrum/Sparrow wallets. Ignored for non-Bitcoin chains. - PlatformExternalAccountCreateRequest: - type: object - required: - - currency - - accountInfo - properties: - currency: - type: string - description: The ISO 4217 currency code - example: USD - platformAccountId: - type: string - description: Your platform's identifier for the account in your system. This can be used to reference the account by your own identifier. - example: ext_acc_123456 - ownershipType: - $ref: '#/components/schemas/OwnershipType' - accountInfo: - $ref: '#/components/schemas/ExternalAccountCreateInfoOneOf' BeneficialOwnerListResponse: type: object required: @@ -25166,11 +25299,10 @@ components: - VERIFICATION.IN_PROGRESS - VERIFICATION.PENDING_MANUAL_REVIEW - VERIFICATION.READY_FOR_VERIFICATION - - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_NOT_REQUIRED - - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_REQUIRED - - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_PENDING_REVIEW - - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_VERIFIED - - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_FAILED + - OWNERSHIP_VERIFICATION.PENDING_REVIEW + - OWNERSHIP_VERIFICATION.VERIFIED + - OWNERSHIP_VERIFICATION.FAILED + - EXTERNAL_ACCOUNT.STATUS_UPDATED - INTERNAL_ACCOUNT.BALANCE_UPDATED - INTERNAL_ACCOUNT.STATUS_UPDATED - INVITATION.CLAIMED @@ -25365,7 +25497,7 @@ components: enum: - INTERNAL_ACCOUNT.BALANCE_UPDATED - INTERNAL_ACCOUNT.STATUS_UPDATED - ExternalAccountWebhook: + ExternalAccountStatusWebhook: allOf: - $ref: '#/components/schemas/BaseWebhook' - type: object @@ -25377,11 +25509,7 @@ components: type: type: string enum: - - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_NOT_REQUIRED - - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_REQUIRED - - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_PENDING_REVIEW - - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_VERIFIED - - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_FAILED + - EXTERNAL_ACCOUNT.STATUS_UPDATED VerificationWebhook: allOf: - $ref: '#/components/schemas/BaseWebhook' @@ -25399,6 +25527,21 @@ components: - VERIFICATION.RESOLVE_ERRORS - VERIFICATION.IN_PROGRESS - VERIFICATION.PENDING_MANUAL_REVIEW + OwnershipVerificationWebhook: + allOf: + - $ref: '#/components/schemas/BaseWebhook' + - type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/OwnershipVerification' + type: + type: string + enum: + - OWNERSHIP_VERIFICATION.PENDING_REVIEW + - OWNERSHIP_VERIFICATION.VERIFIED + - OWNERSHIP_VERIFICATION.FAILED CardStateChangeWebhook: allOf: - $ref: '#/components/schemas/BaseWebhook' diff --git a/openapi/components/schemas/errors/Error400.yaml b/openapi/components/schemas/errors/Error400.yaml index dc073dccd..6185df648 100644 --- a/openapi/components/schemas/errors/Error400.yaml +++ b/openapi/components/schemas/errors/Error400.yaml @@ -30,7 +30,7 @@ properties: | INVALID_PUBKEY_FORMAT | Counterparty Public key format is invalid | | MISSING_REQUIRED_UMA_PARAMETERS | Counterparty required UMA parameters are missing | | SENDER_NOT_ACCEPTED | Sender is not accepted | - | AMOUNT_OUT_OF_RANGE | Amount is out of range | + | AMOUNT_OUT_OF_RANGE | Amount is out of range for the quote or the destination account | | INVALID_CURRENCY | Currency is invalid | | INVALID_TIMESTAMP | Timestamp is invalid | | INVALID_NONCE | Nonce is invalid | @@ -54,10 +54,7 @@ properties: | STABLECOIN_PROVIDER_ACCOUNT_INVALID | The stablecoin provider account link is not usable | | STABLECOIN_PROVIDER_ACCOUNT_REVOKED | The stablecoin provider account link has been revoked | | STABLECOIN_PROVIDER_ACCOUNT_SELECTION_REQUIRED | Multiple active provider account links exist; pass `stablecoinProviderAccountId` to select one | - | OWNERSHIP_TYPE_REQUIRED | `ownershipType` must be provided for this external account | | WALLET_VERIFICATION_REQUIRED | The destination account's ownership must be verified before this transfer can proceed | - | THIRD_PARTY_TRANSFER_LIMIT_EXCEEDED | The transfer exceeds the permitted amount for third-party accounts | - | LIGHTNING_TRANSFER_LIMIT_EXCEEDED | The transfer exceeds the permitted amount for this destination type | enum: - INVALID_INPUT - END_USER_TERMS_VERSION_NOT_FOUND @@ -99,10 +96,7 @@ properties: - STABLECOIN_PROVIDER_ACCOUNT_INVALID - STABLECOIN_PROVIDER_ACCOUNT_REVOKED - STABLECOIN_PROVIDER_ACCOUNT_SELECTION_REQUIRED - - OWNERSHIP_TYPE_REQUIRED - WALLET_VERIFICATION_REQUIRED - - THIRD_PARTY_TRANSFER_LIMIT_EXCEEDED - - LIGHTNING_TRANSFER_LIMIT_EXCEEDED message: type: string description: Error message diff --git a/openapi/components/schemas/external_accounts/ExternalAccount.yaml b/openapi/components/schemas/external_accounts/ExternalAccount.yaml index aa2099cdb..08449b833 100644 --- a/openapi/components/schemas/external_accounts/ExternalAccount.yaml +++ b/openapi/components/schemas/external_accounts/ExternalAccount.yaml @@ -42,8 +42,5 @@ allOf: beneficiaryVerifiedData: $ref: ./BeneficiaryVerifiedData.yaml description: Verified beneficiary data returned by the payment rail, if available - ownershipVerificationStatus: - $ref: ./OwnershipVerificationStatus.yaml - description: The status of ownership verification for this account accountInfo: $ref: ./ExternalAccountInfoOneOf.yaml diff --git a/openapi/components/schemas/external_accounts/ExternalAccountStatus.yaml b/openapi/components/schemas/external_accounts/ExternalAccountStatus.yaml index bc3495857..5b62fd215 100644 --- a/openapi/components/schemas/external_accounts/ExternalAccountStatus.yaml +++ b/openapi/components/schemas/external_accounts/ExternalAccountStatus.yaml @@ -2,6 +2,15 @@ type: string enum: - PENDING - ACTIVE + - PENDING_OWNERSHIP_VERIFICATION - UNDER_REVIEW - INACTIVE -description: Status of an external account +description: | + Status of an external account. + + `PENDING_OWNERSHIP_VERIFICATION` applies to `FIRST_PARTY` self-custody + crypto wallet accounts on platforms subject to EU Travel Rule requirements. + While in this status, the account can be used for transfers below regulatory + thresholds; completing ownership verification (see the Ownership + Verifications API) moves the account to `ACTIVE` and removes the + restriction. diff --git a/openapi/components/schemas/external_accounts/OwnershipVerificationStart.yaml b/openapi/components/schemas/external_accounts/OwnershipVerificationStart.yaml deleted file mode 100644 index 2987e0b97..000000000 --- a/openapi/components/schemas/external_accounts/OwnershipVerificationStart.yaml +++ /dev/null @@ -1,32 +0,0 @@ -type: object -description: >- - The material needed to complete an ownership verification. Which fields are - populated depends on the requested `method`: `messageToSign` for - `WALLET_SIGNATURE`; `verificationLink` and `token` for `LIVENESS`. -required: - - expiresAt -properties: - messageToSign: - type: string - description: >- - `WALLET_SIGNATURE` only. The exact message the wallet must sign, - character-for-character. - example: 'I verify that I control this wallet. Nonce: 019542f5-b3e7-1d02' - verificationLink: - type: string - format: uri - description: >- - `LIVENESS` only. Hosted verification URL to present to the user. - example: https://verify.example.com/session/019542f5-b3e7-1d02 - token: - type: string - description: >- - `LIVENESS` only. Access token for embedding the verification flow in the - platform's own UI, as an alternative to `verificationLink`. - expiresAt: - type: string - format: date-time - description: >- - When this verification session expires. Prompt the user promptly; after - expiry, a new verification must be started. - example: '2025-08-15T15:32:00Z' diff --git a/openapi/components/schemas/external_accounts/OwnershipVerificationStartRequest.yaml b/openapi/components/schemas/external_accounts/OwnershipVerificationStartRequest.yaml deleted file mode 100644 index 447fc9da4..000000000 --- a/openapi/components/schemas/external_accounts/OwnershipVerificationStartRequest.yaml +++ /dev/null @@ -1,10 +0,0 @@ -type: object -description: >- - Starts ownership verification for a self-custody crypto wallet external - account. -required: - - method -properties: - method: - $ref: ./OwnershipVerificationMethod.yaml - description: The verification method to use. diff --git a/openapi/components/schemas/external_accounts/OwnershipVerificationStatus.yaml b/openapi/components/schemas/external_accounts/OwnershipVerificationStatus.yaml deleted file mode 100644 index 199c3ffa0..000000000 --- a/openapi/components/schemas/external_accounts/OwnershipVerificationStatus.yaml +++ /dev/null @@ -1,17 +0,0 @@ -type: string -enum: - - NOT_REQUIRED - - REQUIRED - - PENDING_REVIEW - - FAILED - - VERIFIED -description: | - The status of ownership verification for this external account. - - | Status | Description | - |--------|-------------| - | `NOT_REQUIRED` | Ownership verification does not apply to this account | - | `REQUIRED` | Ownership must be verified before transfers above regulatory thresholds can be sent to this account | - | `PENDING_REVIEW` | A verification was submitted and is under review | - | `FAILED` | The most recent verification attempt failed; a new verification can be started | - | `VERIFIED` | Ownership has been verified; no further action is needed | diff --git a/openapi/components/schemas/ownership_verifications/LivenessOwnershipVerification.yaml b/openapi/components/schemas/ownership_verifications/LivenessOwnershipVerification.yaml new file mode 100644 index 000000000..cbb4db8a5 --- /dev/null +++ b/openapi/components/schemas/ownership_verifications/LivenessOwnershipVerification.yaml @@ -0,0 +1,59 @@ +title: Liveness Ownership Verification +type: object +description: >- + An ownership verification completed by the user through a hosted biometric + verification flow. +required: + - id + - externalAccountId + - method + - status + - verificationLink + - token + - expiresAt + - createdAt +properties: + id: + type: string + description: Unique identifier for this ownership verification + example: OwnershipVerification:019542f5-b3e7-1d02-0000-000000000002 + externalAccountId: + type: string + description: The ID of the external account whose ownership is being verified + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000001 + method: + type: string + enum: + - LIVENESS + description: The verification method. Always `LIVENESS` for this shape. + example: LIVENESS + status: + $ref: ./OwnershipVerificationState.yaml + verificationLink: + type: string + format: uri + description: Hosted verification URL to present to the user. + example: https://verify.example.com/session/019542f5-b3e7-1d02 + token: + type: string + description: >- + Access token for embedding the verification flow in the platform's own + UI, as an alternative to `verificationLink`. + example: eyJhbGciOiJIUzI1NiJ9.example + expiresAt: + type: string + format: date-time + description: >- + When this verification session expires. Prompt the user promptly; after + expiry, a new verification must be started. + example: '2025-08-15T15:32:00Z' + createdAt: + type: string + format: date-time + description: When this verification was created + example: '2025-08-15T15:02:00Z' + updatedAt: + type: string + format: date-time + description: When this verification was last updated + example: '2025-08-15T15:02:00Z' diff --git a/openapi/components/schemas/ownership_verifications/OwnershipVerification.yaml b/openapi/components/schemas/ownership_verifications/OwnershipVerification.yaml new file mode 100644 index 000000000..827239d18 --- /dev/null +++ b/openapi/components/schemas/ownership_verifications/OwnershipVerification.yaml @@ -0,0 +1,11 @@ +description: >- + An ownership verification for a self-custody crypto wallet external account. + The shape is determined by the verification `method`. +oneOf: + - $ref: ./WalletSignatureOwnershipVerification.yaml + - $ref: ./LivenessOwnershipVerification.yaml +discriminator: + propertyName: method + mapping: + WALLET_SIGNATURE: ./WalletSignatureOwnershipVerification.yaml + LIVENESS: ./LivenessOwnershipVerification.yaml diff --git a/openapi/components/schemas/external_accounts/OwnershipVerificationConfirmRequest.yaml b/openapi/components/schemas/ownership_verifications/OwnershipVerificationConfirmRequest.yaml similarity index 83% rename from openapi/components/schemas/external_accounts/OwnershipVerificationConfirmRequest.yaml rename to openapi/components/schemas/ownership_verifications/OwnershipVerificationConfirmRequest.yaml index 25c675387..3f275e2de 100644 --- a/openapi/components/schemas/external_accounts/OwnershipVerificationConfirmRequest.yaml +++ b/openapi/components/schemas/ownership_verifications/OwnershipVerificationConfirmRequest.yaml @@ -1,7 +1,7 @@ type: object description: >- Completes a `WALLET_SIGNATURE` ownership verification by submitting the - signature the wallet produced for the `messageToSign` from the start step. + signature the wallet produced for the verification's `messageToSign`. required: - signature - signedAddress @@ -11,6 +11,7 @@ properties: description: >- The signature produced over the exact `messageToSign` — EIP-191 hex for EVM chains, base64 for Bitcoin, base58-encoded Ed25519 for Solana. + example: '0x52d75f01c9e7b8b2ce2fbcbd21bfeeee7bcd1a2f01ce6b8ad9a67a45e83a8f5d1c' signedAddress: type: string description: The wallet address that signed the message. diff --git a/openapi/components/schemas/ownership_verifications/OwnershipVerificationListResponse.yaml b/openapi/components/schemas/ownership_verifications/OwnershipVerificationListResponse.yaml new file mode 100644 index 000000000..87bfc4b93 --- /dev/null +++ b/openapi/components/schemas/ownership_verifications/OwnershipVerificationListResponse.yaml @@ -0,0 +1,21 @@ +type: object +required: + - data + - hasMore +properties: + data: + type: array + description: List of ownership verifications matching the filter criteria + items: + $ref: ./OwnershipVerification.yaml + hasMore: + type: boolean + description: Indicates if more results are available beyond this page + nextCursor: + type: string + description: >- + Cursor to retrieve the next page of results (only present if + hasMore is true) + totalCount: + type: integer + description: Total number of results matching the criteria diff --git a/openapi/components/schemas/external_accounts/OwnershipVerificationMethod.yaml b/openapi/components/schemas/ownership_verifications/OwnershipVerificationMethod.yaml similarity index 100% rename from openapi/components/schemas/external_accounts/OwnershipVerificationMethod.yaml rename to openapi/components/schemas/ownership_verifications/OwnershipVerificationMethod.yaml diff --git a/openapi/components/schemas/ownership_verifications/OwnershipVerificationRequest.yaml b/openapi/components/schemas/ownership_verifications/OwnershipVerificationRequest.yaml new file mode 100644 index 000000000..d1feafe02 --- /dev/null +++ b/openapi/components/schemas/ownership_verifications/OwnershipVerificationRequest.yaml @@ -0,0 +1,17 @@ +type: object +description: >- + Creates an ownership verification for a self-custody crypto wallet external + account. +required: + - externalAccountId + - method +properties: + externalAccountId: + type: string + description: >- + The ID of the external account (self-custody crypto wallet) whose + ownership is being verified. + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000001 + method: + $ref: ./OwnershipVerificationMethod.yaml + description: The verification method to use. diff --git a/openapi/components/schemas/ownership_verifications/OwnershipVerificationState.yaml b/openapi/components/schemas/ownership_verifications/OwnershipVerificationState.yaml new file mode 100644 index 000000000..3bbfb25fa --- /dev/null +++ b/openapi/components/schemas/ownership_verifications/OwnershipVerificationState.yaml @@ -0,0 +1,16 @@ +type: string +enum: + - PENDING + - PENDING_REVIEW + - VERIFIED + - FAILED +description: | + Current status of this ownership verification. + + | Status | Description | + |--------|-------------| + | `PENDING` | Awaiting the wallet signature or the user completing the verification flow | + | `PENDING_REVIEW` | Submitted and under review | + | `VERIFIED` | Ownership was verified. Terminal | + | `FAILED` | This verification attempt failed. Terminal; start a new verification to retry | +example: PENDING diff --git a/openapi/components/schemas/ownership_verifications/WalletSignatureOwnershipVerification.yaml b/openapi/components/schemas/ownership_verifications/WalletSignatureOwnershipVerification.yaml new file mode 100644 index 000000000..f5c9219a3 --- /dev/null +++ b/openapi/components/schemas/ownership_verifications/WalletSignatureOwnershipVerification.yaml @@ -0,0 +1,51 @@ +title: Wallet Signature Ownership Verification +type: object +description: >- + An ownership verification completed by signing a message with the wallet's + key. +required: + - id + - externalAccountId + - method + - status + - messageToSign + - expiresAt + - createdAt +properties: + id: + type: string + description: Unique identifier for this ownership verification + example: OwnershipVerification:019542f5-b3e7-1d02-0000-000000000001 + externalAccountId: + type: string + description: The ID of the external account whose ownership is being verified + example: ExternalAccount:019542f5-b3e7-1d02-0000-000000000001 + method: + type: string + enum: + - WALLET_SIGNATURE + description: The verification method. Always `WALLET_SIGNATURE` for this shape. + example: WALLET_SIGNATURE + status: + $ref: ./OwnershipVerificationState.yaml + messageToSign: + type: string + description: The exact message the wallet must sign, character-for-character. + example: 'I verify that I control this wallet. Nonce: 019542f5-b3e7-1d02' + expiresAt: + type: string + format: date-time + description: >- + When this verification session expires. Prompt the user promptly; after + expiry, a new verification must be started. + example: '2025-08-15T15:32:00Z' + createdAt: + type: string + format: date-time + description: When this verification was created + example: '2025-08-15T15:02:00Z' + updatedAt: + type: string + format: date-time + description: When this verification was last updated + example: '2025-08-15T15:02:00Z' diff --git a/openapi/components/schemas/webhooks/ExternalAccountStatusWebhook.yaml b/openapi/components/schemas/webhooks/ExternalAccountStatusWebhook.yaml new file mode 100644 index 000000000..6b90db8eb --- /dev/null +++ b/openapi/components/schemas/webhooks/ExternalAccountStatusWebhook.yaml @@ -0,0 +1,12 @@ +allOf: + - $ref: ./BaseWebhook.yaml + - type: object + required: + - data + properties: + data: + $ref: ../external_accounts/ExternalAccount.yaml + type: + type: string + enum: + - EXTERNAL_ACCOUNT.STATUS_UPDATED diff --git a/openapi/components/schemas/webhooks/ExternalAccountWebhook.yaml b/openapi/components/schemas/webhooks/ExternalAccountWebhook.yaml deleted file mode 100644 index 136428934..000000000 --- a/openapi/components/schemas/webhooks/ExternalAccountWebhook.yaml +++ /dev/null @@ -1,16 +0,0 @@ -allOf: - - $ref: ./BaseWebhook.yaml - - type: object - required: - - data - properties: - data: - $ref: ../external_accounts/ExternalAccount.yaml - type: - type: string - enum: - - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_NOT_REQUIRED - - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_REQUIRED - - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_PENDING_REVIEW - - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_VERIFIED - - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_FAILED diff --git a/openapi/components/schemas/webhooks/OwnershipVerificationWebhook.yaml b/openapi/components/schemas/webhooks/OwnershipVerificationWebhook.yaml new file mode 100644 index 000000000..3b714288a --- /dev/null +++ b/openapi/components/schemas/webhooks/OwnershipVerificationWebhook.yaml @@ -0,0 +1,14 @@ +allOf: + - $ref: ./BaseWebhook.yaml + - type: object + required: + - data + properties: + data: + $ref: ../ownership_verifications/OwnershipVerification.yaml + type: + type: string + enum: + - OWNERSHIP_VERIFICATION.PENDING_REVIEW + - OWNERSHIP_VERIFICATION.VERIFIED + - OWNERSHIP_VERIFICATION.FAILED diff --git a/openapi/components/schemas/webhooks/WebhookType.yaml b/openapi/components/schemas/webhooks/WebhookType.yaml index 80ad2f95d..3844b5725 100644 --- a/openapi/components/schemas/webhooks/WebhookType.yaml +++ b/openapi/components/schemas/webhooks/WebhookType.yaml @@ -26,11 +26,10 @@ enum: - VERIFICATION.IN_PROGRESS - VERIFICATION.PENDING_MANUAL_REVIEW - VERIFICATION.READY_FOR_VERIFICATION - - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_NOT_REQUIRED - - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_REQUIRED - - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_PENDING_REVIEW - - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_VERIFIED - - EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_FAILED + - OWNERSHIP_VERIFICATION.PENDING_REVIEW + - OWNERSHIP_VERIFICATION.VERIFIED + - OWNERSHIP_VERIFICATION.FAILED + - EXTERNAL_ACCOUNT.STATUS_UPDATED - INTERNAL_ACCOUNT.BALANCE_UPDATED - INTERNAL_ACCOUNT.STATUS_UPDATED - INVITATION.CLAIMED diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index db1c9dd0d..8156b8482 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -40,6 +40,10 @@ tags: description: Internal account management endpoints for creating and managing internal accounts - name: External Accounts description: External account management endpoints for creating and managing external bank accounts + - name: Ownership Verifications + description: >- + Endpoints for verifying ownership of self-custody crypto wallet external + accounts, via wallet signature or a hosted biometric verification flow. - name: Same-Currency Transfers description: Endpoints for transferring funds between internal and external accounts with the same currency - name: Cross-Currency Transfers @@ -189,18 +193,16 @@ paths: $ref: paths/customers/customers_external_accounts.yaml /customers/external-accounts/{externalAccountId}: $ref: paths/customers/customers_external_accounts_{externalAccountId}.yaml - /customers/external-accounts/{externalAccountId}/verify-ownership: - $ref: paths/customers/customers_external_accounts_{externalAccountId}_verify-ownership.yaml - /customers/external-accounts/{externalAccountId}/verify-ownership/confirm: - $ref: paths/customers/customers_external_accounts_{externalAccountId}_verify-ownership_confirm.yaml /platform/external-accounts: $ref: paths/platform/platform_external_accounts.yaml /platform/external-accounts/{externalAccountId}: $ref: paths/platform/platform_external_accounts_{externalAccountId}.yaml - /platform/external-accounts/{externalAccountId}/verify-ownership: - $ref: paths/platform/platform_external_accounts_{externalAccountId}_verify-ownership.yaml - /platform/external-accounts/{externalAccountId}/verify-ownership/confirm: - $ref: paths/platform/platform_external_accounts_{externalAccountId}_verify-ownership_confirm.yaml + /ownership-verifications: + $ref: paths/ownership_verifications/ownership-verifications.yaml + /ownership-verifications/{verificationId}: + $ref: paths/ownership_verifications/ownership-verifications_{verificationId}.yaml + /ownership-verifications/{verificationId}/confirm: + $ref: paths/ownership_verifications/ownership-verifications_{verificationId}_confirm.yaml /beneficial-owners: $ref: paths/beneficial-owners/beneficial_owners.yaml /beneficial-owners/{beneficialOwnerId}: @@ -397,10 +399,12 @@ webhooks: $ref: webhooks/customer-update.yaml internal-account-status: $ref: webhooks/internal-account-status.yaml - external-account: - $ref: webhooks/external-account.yaml + external-account-status: + $ref: webhooks/external-account-status.yaml verification-update: $ref: webhooks/verification-update.yaml + ownership-verification: + $ref: webhooks/ownership-verification.yaml card-state-change: $ref: webhooks/card-state-change.yaml card-funding-source-change: diff --git a/openapi/paths/customers/customers_external_accounts_{externalAccountId}_verify-ownership.yaml b/openapi/paths/customers/customers_external_accounts_{externalAccountId}_verify-ownership.yaml deleted file mode 100644 index 2a9dcceec..000000000 --- a/openapi/paths/customers/customers_external_accounts_{externalAccountId}_verify-ownership.yaml +++ /dev/null @@ -1,73 +0,0 @@ -parameters: - - name: externalAccountId - in: path - description: The unique identifier of the external account (self-custody crypto wallet) whose ownership is being verified. - required: true - schema: - type: string -post: - summary: Start external account ownership verification - description: | - Begin ownership verification for a `FIRST_PARTY` self-custody crypto wallet - external account. Choose a `method`: - - - `WALLET_SIGNATURE` — the response includes a `messageToSign`; have the - wallet sign it and submit the result to - `POST /customers/external-accounts/{externalAccountId}/verify-ownership/confirm` - to complete verification synchronously. - - `LIVENESS` — the response includes a `verificationLink` (and a `token` - for embedding); the user completes a hosted biometric flow and - verification completes asynchronously. Status transitions are delivered - via `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_*` webhooks and reflected in - the account's `ownershipVerificationStatus`. - - This endpoint is only meaningful for accounts whose - `ownershipVerificationStatus` is `REQUIRED` or `FAILED`. For other accounts, this returns `409`. - operationId: verifyExternalAccountOwnership - tags: - - External Accounts - security: - - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: ../../components/schemas/external_accounts/OwnershipVerificationStartRequest.yaml - responses: - '200': - description: Ownership verification started; the method-specific material is returned. - content: - application/json: - schema: - $ref: ../../components/schemas/external_accounts/OwnershipVerificationStart.yaml - '400': - description: Invalid request - content: - application/json: - schema: - $ref: ../../components/schemas/errors/Error400.yaml - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: ../../components/schemas/errors/Error401.yaml - '404': - description: Customer or external account not found - content: - application/json: - schema: - $ref: ../../components/schemas/errors/Error404.yaml - '409': - description: Ownership verification is not applicable to this external account. - content: - application/json: - schema: - $ref: ../../components/schemas/errors/Error409.yaml - '500': - description: Internal service error - content: - application/json: - schema: - $ref: ../../components/schemas/errors/Error500.yaml diff --git a/openapi/paths/customers/customers_external_accounts_{externalAccountId}_verify-ownership_confirm.yaml b/openapi/paths/customers/customers_external_accounts_{externalAccountId}_verify-ownership_confirm.yaml deleted file mode 100644 index e5895f3f3..000000000 --- a/openapi/paths/customers/customers_external_accounts_{externalAccountId}_verify-ownership_confirm.yaml +++ /dev/null @@ -1,70 +0,0 @@ -parameters: - - name: externalAccountId - in: path - description: The unique identifier of the external account (self-custody crypto wallet) whose ownership is being verified. - required: true - schema: - type: string -post: - summary: Confirm external account ownership verification - description: | - Complete a `WALLET_SIGNATURE` ownership verification by submitting the - signature the wallet produced for the `messageToSign` returned by - `POST /customers/external-accounts/{externalAccountId}/verify-ownership`. - The message must be signed exactly as returned, and the signature must be - submitted before the session's `expiresAt`; after expiry, start a new - verification. - - Returns the updated external account, including its new - `ownershipVerificationStatus`. - - This endpoint is only meaningful for accounts with an in-progress - `WALLET_SIGNATURE` verification. For other accounts, this returns `409`. - operationId: confirmExternalAccountOwnershipVerification - tags: - - External Accounts - security: - - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: ../../components/schemas/external_accounts/OwnershipVerificationConfirmRequest.yaml - responses: - '200': - description: Ownership verification submitted; the updated external account is returned. - content: - application/json: - schema: - $ref: ../../components/schemas/external_accounts/ExternalAccount.yaml - '400': - description: Invalid or expired signature - content: - application/json: - schema: - $ref: ../../components/schemas/errors/Error400.yaml - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: ../../components/schemas/errors/Error401.yaml - '404': - description: Customer or external account not found - content: - application/json: - schema: - $ref: ../../components/schemas/errors/Error404.yaml - '409': - description: Ownership verification is not applicable to this external account. - content: - application/json: - schema: - $ref: ../../components/schemas/errors/Error409.yaml - '500': - description: Internal service error - content: - application/json: - schema: - $ref: ../../components/schemas/errors/Error500.yaml diff --git a/openapi/paths/ownership_verifications/ownership-verifications.yaml b/openapi/paths/ownership_verifications/ownership-verifications.yaml new file mode 100644 index 000000000..fe0b2f100 --- /dev/null +++ b/openapi/paths/ownership_verifications/ownership-verifications.yaml @@ -0,0 +1,133 @@ +post: + summary: Create an ownership verification + description: | + Begin ownership verification for a `FIRST_PARTY` self-custody crypto + wallet external account (customer or platform owned). Choose a `method`: + + - `WALLET_SIGNATURE` — the response includes a `messageToSign`; have the + wallet sign it and submit the result to + `POST /ownership-verifications/{verificationId}/confirm` to complete + verification synchronously. + - `LIVENESS` — the response includes a `verificationLink` (and a `token` + for embedding); the user completes a hosted biometric flow and + verification completes asynchronously. Status transitions are delivered + via `OWNERSHIP_VERIFICATION.*` webhooks or by polling + `GET /ownership-verifications/{verificationId}`. + + Ownership verification applies to accounts in + `PENDING_OWNERSHIP_VERIFICATION` status; completing it moves the account + to `ACTIVE`. For accounts where ownership verification is not applicable, + this returns `409`. + operationId: createOwnershipVerification + tags: + - Ownership Verifications + security: + - BasicAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: ../../components/schemas/ownership_verifications/OwnershipVerificationRequest.yaml + responses: + '201': + description: >- + Ownership verification created; the method-specific material is + returned. + content: + application/json: + schema: + $ref: ../../components/schemas/ownership_verifications/OwnershipVerification.yaml + '400': + description: Bad request - Invalid parameters + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error400.yaml + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error401.yaml + '404': + description: External account not found + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error404.yaml + '409': + description: Ownership verification is not applicable to this external account. + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error409.yaml + '500': + description: Internal service error + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error500.yaml +get: + summary: List ownership verifications + description: > + Retrieve a list of ownership verifications with optional filtering by + external account ID and status. + operationId: listOwnershipVerifications + tags: + - Ownership Verifications + security: + - BasicAuth: [] + parameters: + - name: externalAccountId + in: query + description: Filter by external account ID + required: false + schema: + type: string + - name: status + in: query + description: Filter by verification status + required: false + schema: + $ref: ../../components/schemas/ownership_verifications/OwnershipVerificationState.yaml + - name: limit + in: query + description: Maximum number of results to return (default 20, max 100) + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: cursor + in: query + description: Cursor for pagination (returned from previous request) + required: false + schema: + type: string + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: ../../components/schemas/ownership_verifications/OwnershipVerificationListResponse.yaml + '400': + description: Bad request - Invalid parameters + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error400.yaml + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error401.yaml + '500': + description: Internal service error + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error500.yaml diff --git a/openapi/paths/ownership_verifications/ownership-verifications_{verificationId}.yaml b/openapi/paths/ownership_verifications/ownership-verifications_{verificationId}.yaml new file mode 100644 index 000000000..3e608aea9 --- /dev/null +++ b/openapi/paths/ownership_verifications/ownership-verifications_{verificationId}.yaml @@ -0,0 +1,40 @@ +get: + summary: Get an ownership verification + description: Retrieve details of a specific ownership verification by ID. + operationId: getOwnershipVerification + tags: + - Ownership Verifications + security: + - BasicAuth: [] + parameters: + - name: verificationId + in: path + description: Ownership verification ID + required: true + schema: + type: string + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: ../../components/schemas/ownership_verifications/OwnershipVerification.yaml + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error401.yaml + '404': + description: Ownership verification not found + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error404.yaml + '500': + description: Internal service error + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error500.yaml diff --git a/openapi/paths/ownership_verifications/ownership-verifications_{verificationId}_confirm.yaml b/openapi/paths/ownership_verifications/ownership-verifications_{verificationId}_confirm.yaml new file mode 100644 index 000000000..bb686e60b --- /dev/null +++ b/openapi/paths/ownership_verifications/ownership-verifications_{verificationId}_confirm.yaml @@ -0,0 +1,72 @@ +post: + summary: Confirm an ownership verification + description: | + Complete a `WALLET_SIGNATURE` ownership verification by submitting the + signature the wallet produced for the `messageToSign` returned by + `POST /ownership-verifications`. The message must be signed exactly as + returned, and the signature must be submitted before the session's + `expiresAt`; after expiry, start a new verification. + + This endpoint is only valid for `WALLET_SIGNATURE` verifications in + `PENDING` status. For other verifications, this returns `409`. `LIVENESS` + verifications complete asynchronously — their status is delivered via + `OWNERSHIP_VERIFICATION.*` webhooks or by polling + `GET /ownership-verifications/{verificationId}`. + operationId: confirmOwnershipVerification + tags: + - Ownership Verifications + security: + - BasicAuth: [] + parameters: + - name: verificationId + in: path + description: Ownership verification ID + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: ../../components/schemas/ownership_verifications/OwnershipVerificationConfirmRequest.yaml + responses: + '200': + description: >- + Signature submitted; the updated ownership verification is returned. + content: + application/json: + schema: + $ref: ../../components/schemas/ownership_verifications/OwnershipVerification.yaml + '400': + description: Invalid or expired signature + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error400.yaml + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error401.yaml + '404': + description: Ownership verification not found + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error404.yaml + '409': + description: >- + The verification is not a `WALLET_SIGNATURE` verification in `PENDING` + status. + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error409.yaml + '500': + description: Internal service error + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error500.yaml diff --git a/openapi/paths/platform/platform_external_accounts_{externalAccountId}_verify-ownership.yaml b/openapi/paths/platform/platform_external_accounts_{externalAccountId}_verify-ownership.yaml deleted file mode 100644 index 993e275dc..000000000 --- a/openapi/paths/platform/platform_external_accounts_{externalAccountId}_verify-ownership.yaml +++ /dev/null @@ -1,73 +0,0 @@ -parameters: - - name: externalAccountId - in: path - description: The unique identifier of the platform external account (self-custody crypto wallet) whose ownership is being verified. - required: true - schema: - type: string -post: - summary: Start platform external account ownership verification - description: | - Begin ownership verification for a `FIRST_PARTY` self-custody crypto wallet - external account owned by the platform. Choose a `method`: - - - `WALLET_SIGNATURE` — the response includes a `messageToSign`; have the - wallet sign it and submit the result to - `POST /platform/external-accounts/{externalAccountId}/verify-ownership/confirm` - to complete verification synchronously. - - `LIVENESS` — the response includes a `verificationLink` (and a `token` - for embedding); the user completes a hosted biometric flow and - verification completes asynchronously. Status transitions are delivered - via `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_*` webhooks and reflected in - the account's `ownershipVerificationStatus`. - - This endpoint is only meaningful for accounts whose - `ownershipVerificationStatus` is `REQUIRED` or `FAILED`. For other accounts, this returns `409`. - operationId: verifyPlatformExternalAccountOwnership - tags: - - External Accounts - security: - - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: ../../components/schemas/external_accounts/OwnershipVerificationStartRequest.yaml - responses: - '200': - description: Ownership verification started; the method-specific material is returned. - content: - application/json: - schema: - $ref: ../../components/schemas/external_accounts/OwnershipVerificationStart.yaml - '400': - description: Invalid request - content: - application/json: - schema: - $ref: ../../components/schemas/errors/Error400.yaml - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: ../../components/schemas/errors/Error401.yaml - '404': - description: External account not found - content: - application/json: - schema: - $ref: ../../components/schemas/errors/Error404.yaml - '409': - description: Ownership verification is not applicable to this external account. - content: - application/json: - schema: - $ref: ../../components/schemas/errors/Error409.yaml - '500': - description: Internal service error - content: - application/json: - schema: - $ref: ../../components/schemas/errors/Error500.yaml diff --git a/openapi/paths/platform/platform_external_accounts_{externalAccountId}_verify-ownership_confirm.yaml b/openapi/paths/platform/platform_external_accounts_{externalAccountId}_verify-ownership_confirm.yaml deleted file mode 100644 index 48b04e11f..000000000 --- a/openapi/paths/platform/platform_external_accounts_{externalAccountId}_verify-ownership_confirm.yaml +++ /dev/null @@ -1,70 +0,0 @@ -parameters: - - name: externalAccountId - in: path - description: The unique identifier of the platform external account (self-custody crypto wallet) whose ownership is being verified. - required: true - schema: - type: string -post: - summary: Confirm platform external account ownership verification - description: | - Complete a `WALLET_SIGNATURE` ownership verification by submitting the - signature the wallet produced for the `messageToSign` returned by - `POST /platform/external-accounts/{externalAccountId}/verify-ownership`. - The message must be signed exactly as returned, and the signature must be - submitted before the session's `expiresAt`; after expiry, start a new - verification. - - Returns the updated external account, including its new - `ownershipVerificationStatus`. - - This endpoint is only meaningful for accounts with an in-progress - `WALLET_SIGNATURE` verification. For other accounts, this returns `409`. - operationId: confirmPlatformExternalAccountOwnershipVerification - tags: - - External Accounts - security: - - BasicAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: ../../components/schemas/external_accounts/OwnershipVerificationConfirmRequest.yaml - responses: - '200': - description: Ownership verification submitted; the updated external account is returned. - content: - application/json: - schema: - $ref: ../../components/schemas/external_accounts/ExternalAccount.yaml - '400': - description: Invalid or expired signature - content: - application/json: - schema: - $ref: ../../components/schemas/errors/Error400.yaml - '401': - description: Unauthorized - content: - application/json: - schema: - $ref: ../../components/schemas/errors/Error401.yaml - '404': - description: External account not found - content: - application/json: - schema: - $ref: ../../components/schemas/errors/Error404.yaml - '409': - description: Ownership verification is not applicable to this external account. - content: - application/json: - schema: - $ref: ../../components/schemas/errors/Error409.yaml - '500': - description: Internal service error - content: - application/json: - schema: - $ref: ../../components/schemas/errors/Error500.yaml diff --git a/openapi/webhooks/external-account-status.yaml b/openapi/webhooks/external-account-status.yaml new file mode 100644 index 000000000..b8b02ab0d --- /dev/null +++ b/openapi/webhooks/external-account-status.yaml @@ -0,0 +1,86 @@ +post: + summary: External account status webhook + description: > + Webhook that is called when the status of an external account changes + (e.g., `PENDING_OWNERSHIP_VERIFICATION` → `ACTIVE` after ownership + verification completes). + + 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. + + + ### Event types + + - `EXTERNAL_ACCOUNT.STATUS_UPDATED` — Fired when the status of an external + account changes. The `data` payload contains the full external account + object. + + + operationId: externalAccountStatusWebhook + tags: + - Webhooks + security: + - WebhookSignature: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: ../components/schemas/webhooks/ExternalAccountStatusWebhook.yaml + examples: + statusUpdated: + summary: A wallet account became active after ownership verification + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000042 + type: EXTERNAL_ACCOUNT.STATUS_UPDATED + timestamp: '2025-08-15T14:32:00Z' + data: + id: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 + customerId: Customer:da459a29-1fb7-41ce-a4cb-eb3a3c9fd7a7 + status: ACTIVE + currency: USDC + ownershipType: FIRST_PARTY + accountInfo: + accountType: ETHEREUM_WALLET + address: '0xAbCDEF1234567890aBCdEf1234567890ABcDef12' + 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 diff --git a/openapi/webhooks/external-account.yaml b/openapi/webhooks/external-account.yaml deleted file mode 100644 index 510251415..000000000 --- a/openapi/webhooks/external-account.yaml +++ /dev/null @@ -1,119 +0,0 @@ -post: - summary: External account ownership verification status change - description: > - Webhook that is called when the ownership verification status of an - external account changes. - - 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. - - - ### Event types - - - `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_NOT_REQUIRED` — Fired when an - external account is created and ownership verification does not apply to - it. The `data` payload contains the full external account object. - - - `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_REQUIRED` — Fired when an - external account requires ownership verification before transfers above - regulatory thresholds can be sent to it. The `data` payload contains the - full external account object. - - - `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_PENDING_REVIEW` — Fired when a - submitted ownership verification enters review. The `data` payload contains - the full external account object. - - - `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_VERIFIED` — Fired when ownership - of the external account has been verified. The `data` payload contains the - full external account object. - - - `EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_FAILED` — Fired when an - ownership verification attempt fails; a new verification can be started. - The `data` payload contains the full external account object. - - - operationId: externalAccountWebhook - tags: - - Webhooks - security: - - WebhookSignature: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: ../components/schemas/webhooks/ExternalAccountWebhook.yaml - examples: - ownershipVerified: - summary: Ownership of a self-custody wallet has been verified - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000040 - type: EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_VERIFIED - timestamp: '2025-08-15T14:32:00Z' - data: - id: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - customerId: Customer:da459a29-1fb7-41ce-a4cb-eb3a3c9fd7a7 - status: ACTIVE - currency: USDC - ownershipType: FIRST_PARTY - ownershipVerificationStatus: VERIFIED - accountInfo: - accountType: ETHEREUM_WALLET - address: '0xAbCDEF1234567890aBCdEf1234567890ABcDef12' - ownershipVerificationFailed: - summary: An ownership verification attempt failed - value: - id: Webhook:019542f5-b3e7-1d02-0000-000000000041 - type: EXTERNAL_ACCOUNT.OWNERSHIP_VERIFICATION_FAILED - timestamp: '2025-08-15T14:32:00Z' - data: - id: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965 - customerId: Customer:da459a29-1fb7-41ce-a4cb-eb3a3c9fd7a7 - status: ACTIVE - currency: USDC - ownershipType: FIRST_PARTY - ownershipVerificationStatus: FAILED - accountInfo: - accountType: ETHEREUM_WALLET - address: '0xAbCDEF1234567890aBCdEf1234567890ABcDef12' - 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 diff --git a/openapi/webhooks/ownership-verification.yaml b/openapi/webhooks/ownership-verification.yaml new file mode 100644 index 000000000..1726feb5e --- /dev/null +++ b/openapi/webhooks/ownership-verification.yaml @@ -0,0 +1,109 @@ +post: + summary: Ownership verification status change + description: > + Webhook that is called when the status of an ownership verification + changes. + + 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. + + + ### Event types + + - `OWNERSHIP_VERIFICATION.PENDING_REVIEW` — Fired when a submitted + ownership verification enters review. The `data` payload contains the full + ownership verification object. + + - `OWNERSHIP_VERIFICATION.VERIFIED` — Fired when ownership of the external + account has been verified. The `data` payload contains the full ownership + verification object. + + - `OWNERSHIP_VERIFICATION.FAILED` — Fired when an ownership verification + attempt fails; start a new verification to retry. The `data` payload + contains the full ownership verification object. + + + operationId: ownershipVerificationWebhook + tags: + - Webhooks + security: + - WebhookSignature: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: ../components/schemas/webhooks/OwnershipVerificationWebhook.yaml + examples: + verified: + summary: Ownership of a self-custody wallet has been verified + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000040 + type: OWNERSHIP_VERIFICATION.VERIFIED + timestamp: '2025-08-15T14:32:00Z' + data: + id: OwnershipVerification:019542f5-b3e7-1d02-0000-000000000001 + externalAccountId: ExternalAccount:019542f5-b3e7-1d02-0000-000000000001 + method: WALLET_SIGNATURE + status: VERIFIED + messageToSign: 'I verify that I control this wallet. Nonce: 019542f5-b3e7-1d02' + expiresAt: '2025-08-15T15:32:00Z' + createdAt: '2025-08-15T15:02:00Z' + updatedAt: '2025-08-15T14:32:00Z' + failed: + summary: An ownership verification attempt failed + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000041 + type: OWNERSHIP_VERIFICATION.FAILED + timestamp: '2025-08-15T14:32:00Z' + data: + id: OwnershipVerification:019542f5-b3e7-1d02-0000-000000000002 + externalAccountId: ExternalAccount:019542f5-b3e7-1d02-0000-000000000001 + method: LIVENESS + status: FAILED + verificationLink: https://verify.example.com/session/019542f5-b3e7-1d02 + token: eyJhbGciOiJIUzI1NiJ9.example + expiresAt: '2025-08-15T15:32:00Z' + createdAt: '2025-08-15T15:02:00Z' + updatedAt: '2025-08-15T14:32:00Z' + responses: + '200': + description: > + Webhook received successfully + '400': + description: Bad request + content: + application/json: + schema: + $ref: ../components/schemas/errors/Error400.yaml + '401': + description: Unauthorized - Signature validation failed + content: + application/json: + schema: + $ref: ../components/schemas/errors/Error401.yaml + '409': + description: Conflict - Webhook has already been processed (duplicate id) + content: + application/json: + schema: + $ref: ../components/schemas/errors/Error409.yaml From 39798cfba898b4cb1dbbd922b0526533a6c5046c Mon Sep 17 00:00:00 2001 From: shreyav Date: Thu, 6 Aug 2026 08:36:06 -0700 Subject: [PATCH 4/4] Address review: error-code cleanup, all-status webhook wording, Stainless resource - Revert AMOUNT_OUT_OF_RANGE description to its original text - Rename WALLET_VERIFICATION_REQUIRED to EXTERNAL_ACCOUNT_VERIFICATION_REQUIRED - Document EXTERNAL_ACCOUNT.STATUS_UPDATED as firing on every status transition, not only ownership verification - Register the ownership_verifications resource in .stainless/stainless.yml so the endpoints reach the documented spec and SDKs Co-Authored-By: Claude Fable 5 --- .stainless/stainless.yml | 11 +++++++++++ mintlify/openapi.yaml | 8 ++++---- openapi.yaml | 8 ++++---- openapi/components/schemas/errors/Error400.yaml | 6 +++--- openapi/webhooks/external-account-status.yaml | 7 ++++--- 5 files changed, 26 insertions(+), 14 deletions(-) diff --git a/.stainless/stainless.yml b/.stainless/stainless.yml index 49100e04c..39dde036f 100644 --- a/.stainless/stainless.yml +++ b/.stainless/stainless.yml @@ -533,6 +533,17 @@ resources: submit: post /verifications list: get /verifications retrieve: get /verifications/{verificationId} + ownership_verifications: + models: + ownership_verification: '#/components/schemas/OwnershipVerification' + ownership_verification_list_response: '#/components/schemas/OwnershipVerificationListResponse' + ownership_verification_request: '#/components/schemas/OwnershipVerificationRequest' + ownership_verification_confirm_request: '#/components/schemas/OwnershipVerificationConfirmRequest' + methods: + create: post /ownership-verifications + list: get /ownership-verifications + retrieve: get /ownership-verifications/{verificationId} + confirm: post /ownership-verifications/{verificationId}/confirm discoveries: models: discovery_list_response: '#/components/schemas/DiscoveryListResponse' diff --git a/mintlify/openapi.yaml b/mintlify/openapi.yaml index d8ba938a3..e4cc71371 100644 --- a/mintlify/openapi.yaml +++ b/mintlify/openapi.yaml @@ -10998,7 +10998,7 @@ webhooks: post: summary: External account status webhook description: | - Webhook that is called when the status of an external account changes (e.g., `PENDING_OWNERSHIP_VERIFICATION` → `ACTIVE` after ownership verification completes). + Webhook that is called whenever the status of an external account changes, for any transition between statuses (`PENDING`, `ACTIVE`, `UNDER_REVIEW`, `INACTIVE`, `PENDING_OWNERSHIP_VERIFICATION`) — for example when an account under review becomes active, or when ownership verification completes. This endpoint should be implemented by clients of the Grid API. ### Authentication @@ -12212,7 +12212,7 @@ components: | INVALID_PUBKEY_FORMAT | Counterparty Public key format is invalid | | MISSING_REQUIRED_UMA_PARAMETERS | Counterparty required UMA parameters are missing | | SENDER_NOT_ACCEPTED | Sender is not accepted | - | AMOUNT_OUT_OF_RANGE | Amount is out of range for the quote or the destination account | + | AMOUNT_OUT_OF_RANGE | Amount is out of range | | INVALID_CURRENCY | Currency is invalid | | INVALID_TIMESTAMP | Timestamp is invalid | | INVALID_NONCE | Nonce is invalid | @@ -12236,7 +12236,7 @@ components: | STABLECOIN_PROVIDER_ACCOUNT_INVALID | The stablecoin provider account link is not usable | | STABLECOIN_PROVIDER_ACCOUNT_REVOKED | The stablecoin provider account link has been revoked | | STABLECOIN_PROVIDER_ACCOUNT_SELECTION_REQUIRED | Multiple active provider account links exist; pass `stablecoinProviderAccountId` to select one | - | WALLET_VERIFICATION_REQUIRED | The destination account's ownership must be verified before this transfer can proceed | + | EXTERNAL_ACCOUNT_VERIFICATION_REQUIRED | The destination account's ownership must be verified before this transfer can proceed | enum: - INVALID_INPUT - END_USER_TERMS_VERSION_NOT_FOUND @@ -12278,7 +12278,7 @@ components: - STABLECOIN_PROVIDER_ACCOUNT_INVALID - STABLECOIN_PROVIDER_ACCOUNT_REVOKED - STABLECOIN_PROVIDER_ACCOUNT_SELECTION_REQUIRED - - WALLET_VERIFICATION_REQUIRED + - EXTERNAL_ACCOUNT_VERIFICATION_REQUIRED message: type: string description: Error message diff --git a/openapi.yaml b/openapi.yaml index d8ba938a3..e4cc71371 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -10998,7 +10998,7 @@ webhooks: post: summary: External account status webhook description: | - Webhook that is called when the status of an external account changes (e.g., `PENDING_OWNERSHIP_VERIFICATION` → `ACTIVE` after ownership verification completes). + Webhook that is called whenever the status of an external account changes, for any transition between statuses (`PENDING`, `ACTIVE`, `UNDER_REVIEW`, `INACTIVE`, `PENDING_OWNERSHIP_VERIFICATION`) — for example when an account under review becomes active, or when ownership verification completes. This endpoint should be implemented by clients of the Grid API. ### Authentication @@ -12212,7 +12212,7 @@ components: | INVALID_PUBKEY_FORMAT | Counterparty Public key format is invalid | | MISSING_REQUIRED_UMA_PARAMETERS | Counterparty required UMA parameters are missing | | SENDER_NOT_ACCEPTED | Sender is not accepted | - | AMOUNT_OUT_OF_RANGE | Amount is out of range for the quote or the destination account | + | AMOUNT_OUT_OF_RANGE | Amount is out of range | | INVALID_CURRENCY | Currency is invalid | | INVALID_TIMESTAMP | Timestamp is invalid | | INVALID_NONCE | Nonce is invalid | @@ -12236,7 +12236,7 @@ components: | STABLECOIN_PROVIDER_ACCOUNT_INVALID | The stablecoin provider account link is not usable | | STABLECOIN_PROVIDER_ACCOUNT_REVOKED | The stablecoin provider account link has been revoked | | STABLECOIN_PROVIDER_ACCOUNT_SELECTION_REQUIRED | Multiple active provider account links exist; pass `stablecoinProviderAccountId` to select one | - | WALLET_VERIFICATION_REQUIRED | The destination account's ownership must be verified before this transfer can proceed | + | EXTERNAL_ACCOUNT_VERIFICATION_REQUIRED | The destination account's ownership must be verified before this transfer can proceed | enum: - INVALID_INPUT - END_USER_TERMS_VERSION_NOT_FOUND @@ -12278,7 +12278,7 @@ components: - STABLECOIN_PROVIDER_ACCOUNT_INVALID - STABLECOIN_PROVIDER_ACCOUNT_REVOKED - STABLECOIN_PROVIDER_ACCOUNT_SELECTION_REQUIRED - - WALLET_VERIFICATION_REQUIRED + - EXTERNAL_ACCOUNT_VERIFICATION_REQUIRED message: type: string description: Error message diff --git a/openapi/components/schemas/errors/Error400.yaml b/openapi/components/schemas/errors/Error400.yaml index 6185df648..87ba0ce6a 100644 --- a/openapi/components/schemas/errors/Error400.yaml +++ b/openapi/components/schemas/errors/Error400.yaml @@ -30,7 +30,7 @@ properties: | INVALID_PUBKEY_FORMAT | Counterparty Public key format is invalid | | MISSING_REQUIRED_UMA_PARAMETERS | Counterparty required UMA parameters are missing | | SENDER_NOT_ACCEPTED | Sender is not accepted | - | AMOUNT_OUT_OF_RANGE | Amount is out of range for the quote or the destination account | + | AMOUNT_OUT_OF_RANGE | Amount is out of range | | INVALID_CURRENCY | Currency is invalid | | INVALID_TIMESTAMP | Timestamp is invalid | | INVALID_NONCE | Nonce is invalid | @@ -54,7 +54,7 @@ properties: | STABLECOIN_PROVIDER_ACCOUNT_INVALID | The stablecoin provider account link is not usable | | STABLECOIN_PROVIDER_ACCOUNT_REVOKED | The stablecoin provider account link has been revoked | | STABLECOIN_PROVIDER_ACCOUNT_SELECTION_REQUIRED | Multiple active provider account links exist; pass `stablecoinProviderAccountId` to select one | - | WALLET_VERIFICATION_REQUIRED | The destination account's ownership must be verified before this transfer can proceed | + | EXTERNAL_ACCOUNT_VERIFICATION_REQUIRED | The destination account's ownership must be verified before this transfer can proceed | enum: - INVALID_INPUT - END_USER_TERMS_VERSION_NOT_FOUND @@ -96,7 +96,7 @@ properties: - STABLECOIN_PROVIDER_ACCOUNT_INVALID - STABLECOIN_PROVIDER_ACCOUNT_REVOKED - STABLECOIN_PROVIDER_ACCOUNT_SELECTION_REQUIRED - - WALLET_VERIFICATION_REQUIRED + - EXTERNAL_ACCOUNT_VERIFICATION_REQUIRED message: type: string description: Error message diff --git a/openapi/webhooks/external-account-status.yaml b/openapi/webhooks/external-account-status.yaml index b8b02ab0d..cb53af481 100644 --- a/openapi/webhooks/external-account-status.yaml +++ b/openapi/webhooks/external-account-status.yaml @@ -1,9 +1,10 @@ post: summary: External account status webhook description: > - Webhook that is called when the status of an external account changes - (e.g., `PENDING_OWNERSHIP_VERIFICATION` → `ACTIVE` after ownership - verification completes). + Webhook that is called whenever the status of an external account changes, + for any transition between statuses (`PENDING`, `ACTIVE`, `UNDER_REVIEW`, + `INACTIVE`, `PENDING_OWNERSHIP_VERIFICATION`) — for example when an account + under review becomes active, or when ownership verification completes. This endpoint should be implemented by clients of the Grid API.