From 8c37e14c22ec7c1a43e5c506c7b3b6a286c64b4c Mon Sep 17 00:00:00 2001 From: Peng Ying Date: Fri, 27 Feb 2026 16:21:56 -0800 Subject: [PATCH] feat: standardize webhook schema with consistent envelope and dot-notation types - Adopt OBJECT.EVENT dot-notation for all webhook types (e.g., OUTGOING_PAYMENT.COMPLETED) - Wrap all webhook data under a unified `data` key (Stripe-style full resource embed) - Rename webhook files to match resource-event pattern - Move test webhook endpoint to /sandbox/webhooks/test - Update documentation and examples to match new schema --- mintlify/openapi.yaml | 456 +++++++++--------- mintlify/snippets/kyc/kyc-webhooks.mdx | 113 +++-- mintlify/snippets/webhooks.mdx | 60 +-- openapi.yaml | 456 +++++++++--------- .../customers/BulkUploadWebhookRequest.yaml | 12 - .../webhooks/AccountStatusWebhook.yaml | 27 +- .../schemas/webhooks/BaseWebhook.yaml | 32 +- .../schemas/webhooks/BulkUploadWebhook.yaml | 13 + .../schemas/webhooks/CustomerKycWebhook.yaml | 16 + .../webhooks/IncomingPaymentWebhook.yaml | 35 +- .../webhooks/InvitationClaimedWebhook.yaml | 9 +- .../schemas/webhooks/KycStatusWebhook.yaml | 13 - .../webhooks/OutgoingPaymentWebhook.yaml | 12 +- .../schemas/webhooks/TestWebhookRequest.yaml | 6 +- .../schemas/webhooks/WebhookType.yaml | 26 +- openapi/openapi.yaml | 20 +- .../sandbox_webhooks_test.yaml} | 2 +- openapi/webhooks/account-status.yaml | 32 +- openapi/webhooks/bulk-upload.yaml | 20 +- .../{kyc-status.yaml => customer-kyc.yaml} | 47 +- openapi/webhooks/incoming-payment.yaml | 32 +- openapi/webhooks/invitation-claimed.yaml | 10 +- openapi/webhooks/outgoing-payment.yaml | 53 +- openapi/webhooks/test-webhook.yaml | 7 +- 24 files changed, 783 insertions(+), 726 deletions(-) delete mode 100644 openapi/components/schemas/customers/BulkUploadWebhookRequest.yaml create mode 100644 openapi/components/schemas/webhooks/BulkUploadWebhook.yaml create mode 100644 openapi/components/schemas/webhooks/CustomerKycWebhook.yaml delete mode 100644 openapi/components/schemas/webhooks/KycStatusWebhook.yaml rename openapi/paths/{webhooks/webhooks_test.yaml => sandbox/sandbox_webhooks_test.yaml} (98%) rename openapi/webhooks/{kyc-status.yaml => customer-kyc.yaml} (60%) diff --git a/mintlify/openapi.yaml b/mintlify/openapi.yaml index e149e131..fb4cce6d 100644 --- a/mintlify/openapi.yaml +++ b/mintlify/openapi.yaml @@ -2148,13 +2148,13 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /webhooks/test: + /sandbox/webhooks/test: post: summary: Send a test webhook description: Send a test webhook to the configured endpoint operationId: sendTestWebhook tags: - - Webhooks + - Sandbox security: - BasicAuth: [] responses: @@ -3059,7 +3059,10 @@ webhooks: pendingPayment: summary: Pending payment example requiring approval value: - transaction: + id: Webhook:019542f5-b3e7-1d02-0000-000000000007 + type: INCOMING_PAYMENT.PENDING + timestamp: '2025-08-15T14:32:00Z' + data: id: Transaction:019542f5-b3e7-1d02-0000-000000000005 status: PENDING type: INCOMING @@ -3074,24 +3077,24 @@ webhooks: decimals: 2 customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 platformCustomerId: 18d3e5f7b4a9c2 - reconciliationInstructions: - reference: REF-123456789 counterpartyInformation: FULL_NAME: John Sender BIRTH_DATE: '1985-06-15' NATIONALITY: US - requestedReceiverCustomerInfoFields: - - name: NATIONALITY - mandatory: true - - name: ADDRESS - mandatory: false - timestamp: '2025-08-15T14:32:00Z' - webhookId: Webhook:019542f5-b3e7-1d02-0000-000000000007 - type: INCOMING_PAYMENT + reconciliationInstructions: + reference: REF-123456789 + requestedReceiverCustomerInfoFields: + - name: NATIONALITY + mandatory: true + - name: ADDRESS + mandatory: false incomingCompletedPayment: summary: Completed payment notification value: - transaction: + id: Webhook:019542f5-b3e7-1d02-0000-000000000007 + type: INCOMING_PAYMENT.COMPLETED + timestamp: '2025-08-15T14:32:00Z' + data: id: Transaction:019542f5-b3e7-1d02-0000-000000000005 status: COMPLETED type: INCOMING @@ -3111,9 +3114,6 @@ webhooks: description: Payment for services reconciliationInstructions: reference: REF-123456789 - timestamp: '2025-08-15T14:32:00Z' - webhookId: Webhook:019542f5-b3e7-1d02-0000-000000000007 - type: INCOMING_PAYMENT responses: '200': description: | @@ -3150,7 +3150,7 @@ webhooks: schema: $ref: '#/components/schemas/IncomingPaymentWebhookForbiddenResponse' '409': - description: Conflict - Webhook has already been processed (duplicate webhookId) + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: @@ -3180,7 +3180,7 @@ webhooks: If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - This webhook is informational only and is sent when an outgoing payment completes successfully or fails. + This webhook is informational only and is sent when an outgoing payment completes successfully, fails, or is refunded. operationId: outgoingPaymentWebhook tags: - Webhooks @@ -3196,7 +3196,10 @@ webhooks: outgoingCompletedPayment: summary: Completed outgoing payment value: - transaction: + id: Webhook:019542f5-b3e7-1d02-0000-000000000007 + type: OUTGOING_PAYMENT.COMPLETED + timestamp: '2025-08-15T14:32:00Z' + data: id: Transaction:019542f5-b3e7-1d02-0000-000000000005 status: COMPLETED type: OUTGOING @@ -3218,30 +3221,20 @@ webhooks: decimals: 2 customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 platformCustomerId: 18d3e5f7b4a9c2 - settlementTime: '2025-08-15T14:30:00Z' - createdAt: '2025-08-15T14:25:18Z' - description: 'Payment for invoice #1234' exchangeRate: 0.92 quoteId: Quote:019542f5-b3e7-1d02-0000-000000000006 - paymentInstructions: - - accountOrWalletInfo: - reference: UMA-Q12345-REF - accountType: US_ACCOUNT - accountNumber: 987654321 - routingNumber: 123456789 - accountCategory: CHECKING - bankName: Chase Bank - - accountOrWalletInfo: - accountType: SOLANA_WALLET - assetType: USDC - address: 4Nd1m6Qkq7RfKuE5vQ9qP9Tn6H94Ueqb4xXHzsAbd8Wg - timestamp: '2025-08-15T14:32:00Z' - webhookId: Webhook:019542f5-b3e7-1d02-0000-000000000007 - type: OUTGOING_PAYMENT + settledAt: '2025-08-15T14:30:00Z' + createdAt: '2025-08-15T14:25:18Z' + description: 'Payment for invoice #1234' + paymentInstructions: [] + rateDetails: {} failedPayment: summary: Failed outgoing payment value: - transaction: + id: Webhook:019542f5-b3e7-1d02-0000-000000000007 + type: OUTGOING_PAYMENT.FAILED + timestamp: '2025-08-15T14:32:00Z' + data: id: Transaction:019542f5-b3e7-1d02-0000-000000000005 status: FAILED type: OUTGOING @@ -3258,21 +3251,7 @@ webhooks: platformCustomerId: 18d3e5f7b4a9c2 createdAt: '2025-08-15T14:25:18Z' quoteId: Quote:019542f5-b3e7-1d02-0000-000000000006 - paymentInstructions: - - accountOrWalletInfo: - reference: UMA-Q12345-REF - accountType: US_ACCOUNT - accountNumber: 987654321 - routingNumber: 123456789 - accountCategory: CHECKING - bankName: Chase Bank - - accountOrWalletInfo: - accountType: SOLANA_WALLET - assetType: USDC - address: 4Nd1m6Qkq7RfKuE5vQ9qP9Tn6H94Ueqb4xXHzsAbd8Wg - timestamp: '2025-08-15T14:32:00Z' - webhookId: Webhook:019542f5-b3e7-1d02-0000-000000000007 - type: OUTGOING_PAYMENT + failureReason: INSUFFICIENT_FUNDS responses: '200': description: Webhook received successfully @@ -3289,7 +3268,7 @@ webhooks: schema: $ref: '#/components/schemas/Error401' '409': - description: Conflict - Webhook has already been processed (duplicate webhookId) + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: @@ -3327,9 +3306,10 @@ webhooks: testWebhook: summary: Test webhook example value: - timestamp: '2025-08-15T14:32:00Z' - webhookId: Webhook:019542f5-b3e7-1d02-0000-000000000001 + id: Webhook:019542f5-b3e7-1d02-0000-000000000001 type: TEST + timestamp: '2025-08-15T14:32:00Z' + data: {} responses: '200': description: Webhook received successfully. This confirms your webhook endpoint is properly configured. @@ -3346,7 +3326,7 @@ webhooks: schema: $ref: '#/components/schemas/Error401' '409': - description: Conflict - Webhook has already been processed (duplicate webhookId) + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: @@ -3379,12 +3359,15 @@ webhooks: content: application/json: schema: - $ref: '#/components/schemas/BulkUploadWebhookRequest' + $ref: '#/components/schemas/BulkUploadWebhook' examples: completedUpload: summary: Successful bulk upload completion value: - bulkCustomerImportJob: + id: Webhook:019542f5-b3e7-1d02-0000-000000000008 + type: BULK_UPLOAD.COMPLETED + timestamp: '2025-08-15T14:32:00Z' + data: jobId: Job:019542f5-b3e7-1d02-0000-000000000006 status: COMPLETED progress: @@ -3393,13 +3376,13 @@ webhooks: successful: 5000 failed: 0 errors: [] - webhookId: Webhook:019542f5-b3e7-1d02-0000-000000000008 - type: BULK_UPLOAD - timestamp: '2025-08-15T14:32:00Z' failedUpload: summary: Failed bulk upload value: - bulkCustomerImportJob: + id: Webhook:019542f5-b3e7-1d02-0000-000000000008 + type: BULK_UPLOAD.FAILED + timestamp: '2025-08-15T14:32:00Z' + data: jobId: Job:019542f5-b3e7-1d02-0000-000000000006 status: FAILED progress: @@ -3415,9 +3398,6 @@ webhooks: details: reason: missing_required_column column: umaAddress - webhookId: Webhook:019542f5-b3e7-1d02-0000-000000000008 - type: BULK_UPLOAD - timestamp: '2025-08-15T14:32:00Z' responses: '200': description: Webhook received successfully @@ -3434,7 +3414,7 @@ webhooks: schema: $ref: '#/components/schemas/Error401' '409': - description: Conflict - Webhook has already been processed (duplicate webhookId) + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: @@ -3481,7 +3461,10 @@ webhooks: claimedInvitation: summary: Invitation claimed notification value: - invitation: + id: Webhook:019542f5-b3e7-1d02-0000-000000000008 + type: INVITATION.CLAIMED + timestamp: '2025-09-01T15:45:00Z' + data: code: 019542f5 createdAt: '2025-09-01T14:30:00Z' claimedAt: '2025-09-01T15:45:00Z' @@ -3489,9 +3472,6 @@ webhooks: inviteeUma: $invitee@uma.domain status: CLAIMED url: https://uma.me/i/019542f5 - timestamp: '2025-09-01T15:45:00Z' - webhookId: Webhook:019542f5-b3e7-1d02-0000-000000000008 - type: INVITATION_CLAIMED responses: '200': description: Webhook received successfully @@ -3508,7 +3488,7 @@ webhooks: schema: $ref: '#/components/schemas/Error401' '409': - description: Conflict - Webhook has already been processed (duplicate webhookId) + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: @@ -3552,16 +3532,51 @@ webhooks: content: application/json: schema: - $ref: '#/components/schemas/KycStatusWebhook' + $ref: '#/components/schemas/CustomerKycWebhook' examples: kycApprovedWebhook: summary: When a customer KYC has been approved value: - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - kycStatus: APPROVED - type: KYC_STATUS + id: Webhook:019542f5-b3e7-1d02-0000-000000000007 + type: CUSTOMER.KYC_APPROVED timestamp: '2025-08-15T14:32:00Z' - webhookId: Webhook:019542f5-b3e7-1d02-0000-000000000007 + data: + id: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCustomerId: 9f84e0c2a72c4fa + customerType: INDIVIDUAL + umaAddress: $john.doe@uma.domain.com + kycStatus: APPROVED + fullName: John Michael Doe + birthDate: '1990-01-15' + nationality: US + address: + line1: 123 Main Street + line2: Apt 4B + city: San Francisco + state: CA + postalCode: '94105' + country: US + createdAt: '2025-07-21T17:32:28Z' + updatedAt: '2025-07-21T17:32:28Z' + isDeleted: false + kycRejectedWebhook: + summary: When a customer KYC has been rejected + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000008 + type: CUSTOMER.KYC_REJECTED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Customer:019542f5-b3e7-1d02-0000-000000000002 + platformCustomerId: 4b7c1e9d3f5a8e2 + customerType: INDIVIDUAL + umaAddress: $jane.smith@uma.domain.com + kycStatus: REJECTED + fullName: Jane Smith + birthDate: '1988-03-22' + nationality: US + createdAt: '2025-07-21T17:32:28Z' + updatedAt: '2025-08-15T14:32:00Z' + isDeleted: false responses: '200': description: | @@ -3579,16 +3594,16 @@ webhooks: schema: $ref: '#/components/schemas/Error401' '409': - description: Conflict - Webhook has already been processed (duplicate webhookId) + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: $ref: '#/components/schemas/Error409' account-status: post: - summary: Account status notification webhook + summary: Account balance update webhook description: | - Webhook that is called when the balance of an account changes + Webhook that is called when the balance of an account changes. This endpoint should be implemented by clients of the Grid API. ### Authentication @@ -3601,7 +3616,7 @@ webhooks: If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - ### Account status + ### Account balance update When the balance of an internal account changes, we will push a notification with information on the account, the new balance, and who the account belongs to. operationId: accountStatusWebhook tags: @@ -3618,27 +3633,29 @@ webhooks: balanceDecrease: summary: A transaction just cleared a customer account and the balance has decreased value: - account: - accountId: Account:019542f5-b3e7-1d02-0000-000000000005 - oldBalance: - amount: 50000 + id: Webhook:019542f5-b3e7-1d02-0000-000000000007 + type: ACCOUNT.BALANCE_UPDATED + timestamp: '2025-08-15T14:32:00Z' + data: + id: InternalAccount:019542f5-b3e7-1d02-0000-000000000005 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + balance: + amount: 10000 currency: code: USD name: United States Dollar symbol: $ decimals: 2 - newBalance: - amount: 10000 + oldBalance: + amount: 50000 currency: code: USD name: United States Dollar symbol: $ decimals: 2 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: 019542f5-b3e7-1d02-0000-000000000001 - timestamp: '2025-08-15T14:32:00Z' - webhookId: Webhook:019542f5-b3e7-1d02-0000-000000000007 - type: ACCOUNT_STATUS + fundingPaymentInstructions: [] + createdAt: '2025-08-01T10:00:00Z' + updatedAt: '2025-08-15T14:32:00Z' responses: '200': description: | @@ -3656,7 +3673,7 @@ webhooks: schema: $ref: '#/components/schemas/Error401' '409': - description: Conflict - Webhook has already been processed (duplicate webhookId) + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: @@ -3673,7 +3690,6 @@ components: name: X-Grid-Signature description: | Secp256r1 (P-256) asymmetric signature of the webhook payload, which can be used 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 @@ -3682,18 +3698,6 @@ components: If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. schemas: - AllErrors: - anyOf: - - $ref: '#/components/schemas/Error400' - - $ref: '#/components/schemas/Error401' - - $ref: '#/components/schemas/Error403' - - $ref: '#/components/schemas/Error404' - - $ref: '#/components/schemas/Error409' - - $ref: '#/components/schemas/Error410' - - $ref: '#/components/schemas/Error412' - - $ref: '#/components/schemas/Error424' - - $ref: '#/components/schemas/Error500' - - $ref: '#/components/schemas/Error501' CustomerInfoFieldName: type: string enum: @@ -8492,127 +8496,72 @@ components: description: A list of permissions to grant to the token items: $ref: '#/components/schemas/Permission' - IncomingPaymentWebhook: - allOf: - - $ref: '#/components/schemas/BaseWebhook' - - type: object - required: - - transaction - properties: - transaction: - $ref: '#/components/schemas/IncomingTransaction' - type: - $ref: '#/components/schemas/WebhookType' - description: Type of webhook event - example: INCOMING_PAYMENT - requestedReceiverCustomerInfoFields: - type: array - items: - $ref: '#/components/schemas/CounterpartyFieldDefinition' - description: Information required by the sender's VASP about the recipient. Platform must provide these in the 200 OK response if approving. Note that this only includes fields which Grid does not already have from initial customer registration. + WebhookType: + type: string + enum: + - OUTGOING_PAYMENT.COMPLETED + - OUTGOING_PAYMENT.FAILED + - OUTGOING_PAYMENT.REFUNDED + - INCOMING_PAYMENT.PENDING + - INCOMING_PAYMENT.COMPLETED + - INCOMING_PAYMENT.FAILED + - CUSTOMER.KYC_APPROVED + - CUSTOMER.KYC_REJECTED + - CUSTOMER.KYC_SUBMITTED + - CUSTOMER.KYC_MANUALLY_APPROVED + - CUSTOMER.KYC_MANUALLY_REJECTED + - ACCOUNT.BALANCE_UPDATED + - INVITATION.CLAIMED + - BULK_UPLOAD.COMPLETED + - BULK_UPLOAD.FAILED + - TEST + description: Type of webhook event in OBJECT.EVENT dot-notation. The part before the dot identifies the resource, the part after identifies the event. This lets consumers route purely on type without inspecting data.status. BaseWebhook: type: object required: - timestamp - id - type + - data properties: - timestamp: - type: string - format: date-time - description: ISO8601 timestamp when the webhook was sent (can be used to prevent replay attacks) - example: '2025-08-15T14:32:00Z' id: type: string description: Unique identifier for this webhook delivery (can be used for idempotency) example: Webhook:019542f5-b3e7-1d02-0000-000000000007 type: $ref: '#/components/schemas/WebhookType' - description: Type of webhook event - discriminator: - propertyName: type - mapping: - INCOMING_PAYMENT: '#/components/schemas/IncomingPaymentWebhook' - OUTGOING_PAYMENT: '#/components/schemas/OutgoingPaymentWebhook' - TEST: '#/components/schemas/TestWebhookRequest' - BULK_UPLOAD: '#/components/schemas/BulkUploadWebhookRequest' - INVITATION_CLAIMED: '#/components/schemas/InvitationClaimedWebhook' - KYC_STATUS: '#/components/schemas/KycStatusWebhook' - WebhookType: - type: string - enum: - - INCOMING_PAYMENT - - OUTGOING_PAYMENT - - TEST - - BULK_UPLOAD - - INVITATION_CLAIMED - - KYC_STATUS - - ACCOUNT_STATUS - description: Type of webhook event, used by the receiver to identify which webhook is being received - OutgoingPaymentWebhook: - allOf: - - $ref: '#/components/schemas/BaseWebhook' - - type: object - required: - - transaction - properties: - transaction: - $ref: '#/components/schemas/OutgoingTransaction' - type: - $ref: '#/components/schemas/WebhookType' - description: Type of webhook event - example: OUTGOING_PAYMENT - TestWebhookRequest: - allOf: - - $ref: '#/components/schemas/BaseWebhook' - - type: object - properties: - type: - $ref: '#/components/schemas/WebhookType' - description: Type of webhook event - example: TEST - BulkUploadWebhookRequest: - allOf: - - $ref: '#/components/schemas/BaseWebhook' - - type: object - required: - - bulkCustomerImportJob - properties: - bulkCustomerImportJob: - $ref: '#/components/schemas/BulkCustomerImportJob' - type: - $ref: '#/components/schemas/WebhookType' - description: Type of webhook event - example: BULK_UPLOAD - InvitationClaimedWebhook: + description: Status-specific event type in OBJECT.EVENT dot-notation (e.g., OUTGOING_PAYMENT.COMPLETED) + timestamp: + type: string + format: date-time + description: ISO 8601 timestamp of when the webhook was sent + example: '2025-08-15T14:32:00Z' + data: + type: object + description: The resource object. Contains the full resource as the corresponding GET endpoint would return it. + IncomingPaymentWebhook: allOf: - $ref: '#/components/schemas/BaseWebhook' - type: object required: - - invitation + - data properties: - invitation: - $ref: '#/components/schemas/UmaInvitation' + data: + allOf: + - $ref: '#/components/schemas/IncomingTransaction' + - type: object + properties: + requestedReceiverCustomerInfoFields: + type: array + items: + $ref: '#/components/schemas/CounterpartyFieldDefinition' + description: Information required by the sender's VASP about the recipient. Platform must provide these in the 200 OK response if approving. Note that this only includes fields which Grid does not already have from initial customer registration. type: type: string enum: - - INVITATION_CLAIMED - description: Type of webhook event - example: INVITATION_CLAIMED - KycStatusWebhook: - allOf: - - $ref: '#/components/schemas/BaseWebhook' - - type: object - required: - - customerId - - kycStatus - properties: - customerId: - type: string - description: System generated id of the customer - example: Customer:019542f5-b3e7-1d02-0000-000000000001 - kycStatus: - $ref: '#/components/schemas/KycStatus' + - INCOMING_PAYMENT.PENDING + - INCOMING_PAYMENT.COMPLETED + - INCOMING_PAYMENT.FAILED IncomingPaymentWebhookResponse: type: object properties: @@ -8642,25 +8591,90 @@ components: example: - TAX_ID - REGISTRATION_NUMBER - AccountStatusWebhook: + OutgoingPaymentWebhook: allOf: - $ref: '#/components/schemas/BaseWebhook' - type: object required: - - accountId + - data properties: - accountId: + data: + $ref: '#/components/schemas/OutgoingTransaction' + type: type: string - description: The id of the account whose balance has changed - oldBalance: - $ref: '#/components/schemas/CurrencyAmount' - newBalance: - $ref: '#/components/schemas/CurrencyAmount' - customerId: + enum: + - OUTGOING_PAYMENT.COMPLETED + - OUTGOING_PAYMENT.FAILED + - OUTGOING_PAYMENT.REFUNDED + TestWebhookRequest: + allOf: + - $ref: '#/components/schemas/BaseWebhook' + - type: object + properties: + type: type: string - description: The ID of the customer associated with the internal account - example: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: + enum: + - TEST + BulkUploadWebhook: + allOf: + - $ref: '#/components/schemas/BaseWebhook' + - type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/BulkCustomerImportJob' + type: + type: string + enum: + - BULK_UPLOAD.COMPLETED + - BULK_UPLOAD.FAILED + InvitationClaimedWebhook: + allOf: + - $ref: '#/components/schemas/BaseWebhook' + - type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/UmaInvitation' + type: type: string - description: The ID of the customer as associated in your platform - example: 019542f5-b3e7-1d02-0000-000000000001 + enum: + - INVITATION.CLAIMED + CustomerKycWebhook: + allOf: + - $ref: '#/components/schemas/BaseWebhook' + - type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/IndividualCustomer' + type: + type: string + enum: + - CUSTOMER.KYC_APPROVED + - CUSTOMER.KYC_REJECTED + - CUSTOMER.KYC_SUBMITTED + - CUSTOMER.KYC_MANUALLY_APPROVED + - CUSTOMER.KYC_MANUALLY_REJECTED + AccountStatusWebhook: + allOf: + - $ref: '#/components/schemas/BaseWebhook' + - type: object + required: + - data + properties: + data: + allOf: + - $ref: '#/components/schemas/InternalAccount' + - type: object + properties: + oldBalance: + $ref: '#/components/schemas/CurrencyAmount' + description: The account balance before the change + type: + type: string + enum: + - ACCOUNT.BALANCE_UPDATED diff --git a/mintlify/snippets/kyc/kyc-webhooks.mdx b/mintlify/snippets/kyc/kyc-webhooks.mdx index e380222c..678edea8 100644 --- a/mintlify/snippets/kyc/kyc-webhooks.mdx +++ b/mintlify/snippets/kyc/kyc-webhooks.mdx @@ -8,31 +8,52 @@ For regulated platforms, customers are created with `APPROVED` KYC status by def ```json { - "webhookId": "Webhook:019542f5-b3e7-1d02-0000-000000000020", - "type": "KYC_STATUS", - "timestamp": "2023-07-21T17:32:28Z", - "customerId": "Customer:019542f5-b3e7-1d02-0000-000000000001", - "kycStatus": "APPROVED", - "platformCustomerId": "1234567" + "id": "Webhook:019542f5-b3e7-1d02-0000-000000000020", + "type": "CUSTOMER.KYC_APPROVED", + "timestamp": "2025-07-21T17:32:28Z", + "data": { + "id": "Customer:019542f5-b3e7-1d02-0000-000000000001", + "platformCustomerId": "9f84e0c2a72c4fa", + "customerType": "INDIVIDUAL", + "umaAddress": "$john.doe@uma.domain.com", + "kycStatus": "APPROVED", + "fullName": "John Michael Doe", + "birthDate": "1990-01-15", + "nationality": "US", + "address": { + "line1": "123 Main Street", + "line2": "Apt 4B", + "city": "San Francisco", + "state": "CA", + "postalCode": "94105", + "country": "US" + }, + "createdAt": "2025-07-21T17:32:28Z", + "updatedAt": "2025-07-21T17:32:28Z", + "isDeleted": false + } } ``` **Webhook Headers:** - `Content-Type: application/json` -- `X-Webhook-Signature: sha256=abc123...` +- `X-Grid-Signature: {"v": "1", "s": "base64_signature..."}` + + +Unique identifier for this webhook delivery. Use this for idempotency to prevent processing duplicate webhooks. + - -System-generated unique identifier of the customer whose KYC status has changed. + +Status-specific event type. KYC webhooks use `CUSTOMER.*` types: +- `CUSTOMER.KYC_APPROVED`: Customer verification completed successfully +- `CUSTOMER.KYC_REJECTED`: Customer verification was rejected +- `CUSTOMER.KYC_SUBMITTED`: KYC verification was initially submitted +- `CUSTOMER.KYC_MANUALLY_APPROVED`: Customer was manually approved by platform +- `CUSTOMER.KYC_MANUALLY_REJECTED`: Customer was manually rejected by platform - -Final KYC verification status. Webhooks are only sent for final states: -- `APPROVED`: Customer verification completed successfully -- `REJECTED`: Customer verification was rejected -- `EXPIRED`: KYC verification has expired and needs renewal -- `CANCELED`: Verification process was canceled -- `MANUALLY_APPROVED`: Customer was manually approved by platform -- `MANUALLY_REJECTED`: Customer was manually rejected by platform + +The full customer resource object, same as the corresponding `GET /customers/{id}` endpoint would return. Includes all customer fields such as `id`, `kycStatus`, `fullName`, `birthDate`, `nationality`, `address`, etc. @@ -43,50 +64,40 @@ Intermediate states like `PENDING_REVIEW` do not trigger webhook notifications. ```javascript // Example webhook handler for KYC status updates // Note: Only final states trigger webhook notifications -app.post('/webhooks/kyc-status', (req, res) => { - const { customerId, kycStatus } = req.body; - - switch (kycStatus) { - case 'APPROVED': +app.post('/webhooks/kyc-status', async (req, res) => { + const { type, data } = req.body; + + switch (type) { + case 'CUSTOMER.KYC_APPROVED': // Activate customer account - await activateCustomer(customerId); - await sendWelcomeEmail(customerId); + await activateCustomer(data.id); + await sendWelcomeEmail(data.id); break; - - case 'REJECTED': + + case 'CUSTOMER.KYC_REJECTED': // Notify support and customer - await notifySupport(customerId, 'KYC_REJECTED'); - await sendRejectionEmail(customerId); + await notifySupport(data.id, 'KYC_REJECTED'); + await sendRejectionEmail(data.id); break; - - case 'MANUALLY_APPROVED': + + case 'CUSTOMER.KYC_MANUALLY_APPROVED': // Handle manual approval - await activateCustomer(customerId); - await sendWelcomeEmail(customerId); + await activateCustomer(data.id); + await sendWelcomeEmail(data.id); break; - - case 'MANUALLY_REJECTED': + + case 'CUSTOMER.KYC_MANUALLY_REJECTED': // Handle manual rejection - await notifySupport(customerId, 'KYC_MANUALLY_REJECTED'); - await sendRejectionEmail(customerId); - break; - - case 'EXPIRED': - // Handle expired KYC - await notifyCustomerForReKyc(customerId); - break; - - case 'CANCELED': - // Handle canceled verification - await logKycCancelation(customerId); + await notifySupport(data.id, 'KYC_MANUALLY_REJECTED'); + await sendRejectionEmail(data.id); break; - + default: - // Log unexpected statuses - console.log(`Unexpected KYC status ${kycStatus} for customer ${customerId}`); + // Log unexpected types + console.log(`Unexpected webhook type ${type} for customer ${data.id}`); } - + res.status(200).send('OK'); }); ``` - \ No newline at end of file + diff --git a/mintlify/snippets/webhooks.mdx b/mintlify/snippets/webhooks.mdx index b44a1741..b087ddbf 100644 --- a/mintlify/snippets/webhooks.mdx +++ b/mintlify/snippets/webhooks.mdx @@ -29,11 +29,11 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE... app.post('/webhooks/uma', (req, res) => { const signatureHeader = req.header('X-Grid-Signature'); - + if (!signatureHeader) { return res.status(401).json({ error: 'Signature missing' }); } - + try { let signature: Buffer; try { @@ -49,7 +49,7 @@ app.post('/webhooks/uma', (req, res) => { // If JSON parsing fails, treat as direct base64 signature = Buffer.from(signatureHeader, "base64"); } - + // Create verifier with the public key and correct algorithm const verifier = crypto.createVerify("SHA256"); const payload = await request.text(); @@ -65,22 +65,22 @@ app.post('/webhooks/uma', (req, res) => { }, signature, ); - + if (!isValid) { return res.status(401).json({ error: 'Invalid signature' }); } - + // Webhook is verified, process it based on type const webhookData = req.body; - - if (webhookData.type === 'INCOMING_PAYMENT') { + + if (webhookData.type.startsWith('INCOMING_PAYMENT.')) { // Process incoming payment webhook // ... - } else if (webhookData.type === 'OUTGOING_PAYMENT') { + } else if (webhookData.type.startsWith('OUTGOING_PAYMENT.')) { // Process outgoing payment webhook // ... } - + // Acknowledge receipt of the webhook return res.status(200).json({ received: true }); } catch (error) { @@ -121,19 +121,19 @@ def handle_webhook(): signature = request.headers.get('X-Grid-Signature') if not signature: return jsonify({'error': 'Signature missing'}), 401 - + try: # Get the raw request body request_body = request.get_data() - + # Create a SHA-256 hash of the request body hash_obj = hashes.Hash(hashes.SHA256()) hash_obj.update(request_body) digest = hash_obj.finalize() - + # Decode the base64 signature signature_bytes = base64.b64decode(signature) - + # Verify the signature try: public_key.verify( @@ -143,19 +143,19 @@ def handle_webhook(): ) except Exception as e: return jsonify({'error': 'Invalid signature'}), 401 - + # Webhook is verified, process it based on type webhook_data = request.json - - if webhook_data['type'] == 'INCOMING_PAYMENT': + + if webhook_data['type'].startswith('INCOMING_PAYMENT.'): # Process incoming payment webhook # ... pass - elif webhook_data['type'] == 'OUTGOING_PAYMENT': + elif webhook_data['type'].startswith('OUTGOING_PAYMENT.'): # Process outgoing payment webhook # ... pass - + # Acknowledge receipt of the webhook return jsonify({'received': True}), 200 except Exception as e: @@ -174,10 +174,10 @@ An example of the test webhook payload is shown below: ```json { - "test": true, - "timestamp": "2023-08-15T14:32:00Z", - "webhookId": "Webhook:019542f5-b3e7-1d02-0000-000000000007", - "type": "TEST" + "id": "Webhook:019542f5-b3e7-1d02-0000-000000000007", + "type": "TEST", + "timestamp": "2025-08-15T14:32:00Z", + "data": {} } ``` @@ -187,7 +187,7 @@ You should verify the signature of the webhook using the Grid public key and the - **Always verify signatures**: Never process webhooks without verifying their signatures. - **Use HTTPS**: Ensure your webhook endpoint uses HTTPS to prevent man-in-the-middle attacks. -- **Implement idempotency**: Use the `webhookId` field to prevent processing duplicate webhooks. +- **Implement idempotency**: Use the `id` field to prevent processing duplicate webhooks. - **Timeout handling**: Implement proper timeout handling and respond to webhooks promptly. ## Retry Policy @@ -196,10 +196,10 @@ The Grid API will retry webhooks with the following policy based on the webhook | Webhook Type | Retry Policy | Notes | |-------------|-------------|-------| -| TEST | No retries | Used for testing webhook configuration | -| OUTGOING_PAYMENT | Retry with exponential backoff up to 7 days with maximum interval of 30 mins | No retry on 409 (duplicate webhooks) | -| INCOMING_PAYMENT | Retry with exponential backoff up to 7 days with maximum interval of 30 mins | No retry on: 409 (duplicate webhook) or PENDING status since it is served as an approval mechanism in-flow | -| BULK_UPLOAD | Retry with exponential backoff up to 7 days with maximum interval of 30 mins | No retry on 409 (duplicate webhooks) | -| INVITATION_CLAIMED | Retry with exponential backoff up to 7 days with maximum interval of 30 mins | No retry on 409 (duplicate webhooks) | -| KYC_STATUS | Retry with exponential backoff up to 7 days with maximum interval of 30 mins | No retry on 409 (duplicate webhooks) | -| ACCOUNT_STATUS | Retry with exponential backoff up to 7 days with maximum interval of 30 mins | No retry on 409 (duplicate webhooks) | \ No newline at end of file +| `TEST` | No retries | Used for testing webhook configuration | +| `OUTGOING_PAYMENT.*` | Retry with exponential backoff up to 7 days with maximum interval of 30 mins | No retry on 409 (duplicate webhooks) | +| `INCOMING_PAYMENT.*` | Retry with exponential backoff up to 7 days with maximum interval of 30 mins | No retry on: 409 (duplicate webhook) or PENDING status since it is served as an approval mechanism in-flow | +| `BULK_UPLOAD.*` | Retry with exponential backoff up to 7 days with maximum interval of 30 mins | No retry on 409 (duplicate webhooks) | +| `INVITATION.*` | Retry with exponential backoff up to 7 days with maximum interval of 30 mins | No retry on 409 (duplicate webhooks) | +| `CUSTOMER.*` | Retry with exponential backoff up to 7 days with maximum interval of 30 mins | No retry on 409 (duplicate webhooks) | +| `ACCOUNT.*` | Retry with exponential backoff up to 7 days with maximum interval of 30 mins | No retry on 409 (duplicate webhooks) | diff --git a/openapi.yaml b/openapi.yaml index e149e131..fb4cce6d 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -2148,13 +2148,13 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' - /webhooks/test: + /sandbox/webhooks/test: post: summary: Send a test webhook description: Send a test webhook to the configured endpoint operationId: sendTestWebhook tags: - - Webhooks + - Sandbox security: - BasicAuth: [] responses: @@ -3059,7 +3059,10 @@ webhooks: pendingPayment: summary: Pending payment example requiring approval value: - transaction: + id: Webhook:019542f5-b3e7-1d02-0000-000000000007 + type: INCOMING_PAYMENT.PENDING + timestamp: '2025-08-15T14:32:00Z' + data: id: Transaction:019542f5-b3e7-1d02-0000-000000000005 status: PENDING type: INCOMING @@ -3074,24 +3077,24 @@ webhooks: decimals: 2 customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 platformCustomerId: 18d3e5f7b4a9c2 - reconciliationInstructions: - reference: REF-123456789 counterpartyInformation: FULL_NAME: John Sender BIRTH_DATE: '1985-06-15' NATIONALITY: US - requestedReceiverCustomerInfoFields: - - name: NATIONALITY - mandatory: true - - name: ADDRESS - mandatory: false - timestamp: '2025-08-15T14:32:00Z' - webhookId: Webhook:019542f5-b3e7-1d02-0000-000000000007 - type: INCOMING_PAYMENT + reconciliationInstructions: + reference: REF-123456789 + requestedReceiverCustomerInfoFields: + - name: NATIONALITY + mandatory: true + - name: ADDRESS + mandatory: false incomingCompletedPayment: summary: Completed payment notification value: - transaction: + id: Webhook:019542f5-b3e7-1d02-0000-000000000007 + type: INCOMING_PAYMENT.COMPLETED + timestamp: '2025-08-15T14:32:00Z' + data: id: Transaction:019542f5-b3e7-1d02-0000-000000000005 status: COMPLETED type: INCOMING @@ -3111,9 +3114,6 @@ webhooks: description: Payment for services reconciliationInstructions: reference: REF-123456789 - timestamp: '2025-08-15T14:32:00Z' - webhookId: Webhook:019542f5-b3e7-1d02-0000-000000000007 - type: INCOMING_PAYMENT responses: '200': description: | @@ -3150,7 +3150,7 @@ webhooks: schema: $ref: '#/components/schemas/IncomingPaymentWebhookForbiddenResponse' '409': - description: Conflict - Webhook has already been processed (duplicate webhookId) + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: @@ -3180,7 +3180,7 @@ webhooks: If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - This webhook is informational only and is sent when an outgoing payment completes successfully or fails. + This webhook is informational only and is sent when an outgoing payment completes successfully, fails, or is refunded. operationId: outgoingPaymentWebhook tags: - Webhooks @@ -3196,7 +3196,10 @@ webhooks: outgoingCompletedPayment: summary: Completed outgoing payment value: - transaction: + id: Webhook:019542f5-b3e7-1d02-0000-000000000007 + type: OUTGOING_PAYMENT.COMPLETED + timestamp: '2025-08-15T14:32:00Z' + data: id: Transaction:019542f5-b3e7-1d02-0000-000000000005 status: COMPLETED type: OUTGOING @@ -3218,30 +3221,20 @@ webhooks: decimals: 2 customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 platformCustomerId: 18d3e5f7b4a9c2 - settlementTime: '2025-08-15T14:30:00Z' - createdAt: '2025-08-15T14:25:18Z' - description: 'Payment for invoice #1234' exchangeRate: 0.92 quoteId: Quote:019542f5-b3e7-1d02-0000-000000000006 - paymentInstructions: - - accountOrWalletInfo: - reference: UMA-Q12345-REF - accountType: US_ACCOUNT - accountNumber: 987654321 - routingNumber: 123456789 - accountCategory: CHECKING - bankName: Chase Bank - - accountOrWalletInfo: - accountType: SOLANA_WALLET - assetType: USDC - address: 4Nd1m6Qkq7RfKuE5vQ9qP9Tn6H94Ueqb4xXHzsAbd8Wg - timestamp: '2025-08-15T14:32:00Z' - webhookId: Webhook:019542f5-b3e7-1d02-0000-000000000007 - type: OUTGOING_PAYMENT + settledAt: '2025-08-15T14:30:00Z' + createdAt: '2025-08-15T14:25:18Z' + description: 'Payment for invoice #1234' + paymentInstructions: [] + rateDetails: {} failedPayment: summary: Failed outgoing payment value: - transaction: + id: Webhook:019542f5-b3e7-1d02-0000-000000000007 + type: OUTGOING_PAYMENT.FAILED + timestamp: '2025-08-15T14:32:00Z' + data: id: Transaction:019542f5-b3e7-1d02-0000-000000000005 status: FAILED type: OUTGOING @@ -3258,21 +3251,7 @@ webhooks: platformCustomerId: 18d3e5f7b4a9c2 createdAt: '2025-08-15T14:25:18Z' quoteId: Quote:019542f5-b3e7-1d02-0000-000000000006 - paymentInstructions: - - accountOrWalletInfo: - reference: UMA-Q12345-REF - accountType: US_ACCOUNT - accountNumber: 987654321 - routingNumber: 123456789 - accountCategory: CHECKING - bankName: Chase Bank - - accountOrWalletInfo: - accountType: SOLANA_WALLET - assetType: USDC - address: 4Nd1m6Qkq7RfKuE5vQ9qP9Tn6H94Ueqb4xXHzsAbd8Wg - timestamp: '2025-08-15T14:32:00Z' - webhookId: Webhook:019542f5-b3e7-1d02-0000-000000000007 - type: OUTGOING_PAYMENT + failureReason: INSUFFICIENT_FUNDS responses: '200': description: Webhook received successfully @@ -3289,7 +3268,7 @@ webhooks: schema: $ref: '#/components/schemas/Error401' '409': - description: Conflict - Webhook has already been processed (duplicate webhookId) + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: @@ -3327,9 +3306,10 @@ webhooks: testWebhook: summary: Test webhook example value: - timestamp: '2025-08-15T14:32:00Z' - webhookId: Webhook:019542f5-b3e7-1d02-0000-000000000001 + id: Webhook:019542f5-b3e7-1d02-0000-000000000001 type: TEST + timestamp: '2025-08-15T14:32:00Z' + data: {} responses: '200': description: Webhook received successfully. This confirms your webhook endpoint is properly configured. @@ -3346,7 +3326,7 @@ webhooks: schema: $ref: '#/components/schemas/Error401' '409': - description: Conflict - Webhook has already been processed (duplicate webhookId) + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: @@ -3379,12 +3359,15 @@ webhooks: content: application/json: schema: - $ref: '#/components/schemas/BulkUploadWebhookRequest' + $ref: '#/components/schemas/BulkUploadWebhook' examples: completedUpload: summary: Successful bulk upload completion value: - bulkCustomerImportJob: + id: Webhook:019542f5-b3e7-1d02-0000-000000000008 + type: BULK_UPLOAD.COMPLETED + timestamp: '2025-08-15T14:32:00Z' + data: jobId: Job:019542f5-b3e7-1d02-0000-000000000006 status: COMPLETED progress: @@ -3393,13 +3376,13 @@ webhooks: successful: 5000 failed: 0 errors: [] - webhookId: Webhook:019542f5-b3e7-1d02-0000-000000000008 - type: BULK_UPLOAD - timestamp: '2025-08-15T14:32:00Z' failedUpload: summary: Failed bulk upload value: - bulkCustomerImportJob: + id: Webhook:019542f5-b3e7-1d02-0000-000000000008 + type: BULK_UPLOAD.FAILED + timestamp: '2025-08-15T14:32:00Z' + data: jobId: Job:019542f5-b3e7-1d02-0000-000000000006 status: FAILED progress: @@ -3415,9 +3398,6 @@ webhooks: details: reason: missing_required_column column: umaAddress - webhookId: Webhook:019542f5-b3e7-1d02-0000-000000000008 - type: BULK_UPLOAD - timestamp: '2025-08-15T14:32:00Z' responses: '200': description: Webhook received successfully @@ -3434,7 +3414,7 @@ webhooks: schema: $ref: '#/components/schemas/Error401' '409': - description: Conflict - Webhook has already been processed (duplicate webhookId) + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: @@ -3481,7 +3461,10 @@ webhooks: claimedInvitation: summary: Invitation claimed notification value: - invitation: + id: Webhook:019542f5-b3e7-1d02-0000-000000000008 + type: INVITATION.CLAIMED + timestamp: '2025-09-01T15:45:00Z' + data: code: 019542f5 createdAt: '2025-09-01T14:30:00Z' claimedAt: '2025-09-01T15:45:00Z' @@ -3489,9 +3472,6 @@ webhooks: inviteeUma: $invitee@uma.domain status: CLAIMED url: https://uma.me/i/019542f5 - timestamp: '2025-09-01T15:45:00Z' - webhookId: Webhook:019542f5-b3e7-1d02-0000-000000000008 - type: INVITATION_CLAIMED responses: '200': description: Webhook received successfully @@ -3508,7 +3488,7 @@ webhooks: schema: $ref: '#/components/schemas/Error401' '409': - description: Conflict - Webhook has already been processed (duplicate webhookId) + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: @@ -3552,16 +3532,51 @@ webhooks: content: application/json: schema: - $ref: '#/components/schemas/KycStatusWebhook' + $ref: '#/components/schemas/CustomerKycWebhook' examples: kycApprovedWebhook: summary: When a customer KYC has been approved value: - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - kycStatus: APPROVED - type: KYC_STATUS + id: Webhook:019542f5-b3e7-1d02-0000-000000000007 + type: CUSTOMER.KYC_APPROVED timestamp: '2025-08-15T14:32:00Z' - webhookId: Webhook:019542f5-b3e7-1d02-0000-000000000007 + data: + id: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCustomerId: 9f84e0c2a72c4fa + customerType: INDIVIDUAL + umaAddress: $john.doe@uma.domain.com + kycStatus: APPROVED + fullName: John Michael Doe + birthDate: '1990-01-15' + nationality: US + address: + line1: 123 Main Street + line2: Apt 4B + city: San Francisco + state: CA + postalCode: '94105' + country: US + createdAt: '2025-07-21T17:32:28Z' + updatedAt: '2025-07-21T17:32:28Z' + isDeleted: false + kycRejectedWebhook: + summary: When a customer KYC has been rejected + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000008 + type: CUSTOMER.KYC_REJECTED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Customer:019542f5-b3e7-1d02-0000-000000000002 + platformCustomerId: 4b7c1e9d3f5a8e2 + customerType: INDIVIDUAL + umaAddress: $jane.smith@uma.domain.com + kycStatus: REJECTED + fullName: Jane Smith + birthDate: '1988-03-22' + nationality: US + createdAt: '2025-07-21T17:32:28Z' + updatedAt: '2025-08-15T14:32:00Z' + isDeleted: false responses: '200': description: | @@ -3579,16 +3594,16 @@ webhooks: schema: $ref: '#/components/schemas/Error401' '409': - description: Conflict - Webhook has already been processed (duplicate webhookId) + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: $ref: '#/components/schemas/Error409' account-status: post: - summary: Account status notification webhook + summary: Account balance update webhook description: | - Webhook that is called when the balance of an account changes + Webhook that is called when the balance of an account changes. This endpoint should be implemented by clients of the Grid API. ### Authentication @@ -3601,7 +3616,7 @@ webhooks: If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - ### Account status + ### Account balance update When the balance of an internal account changes, we will push a notification with information on the account, the new balance, and who the account belongs to. operationId: accountStatusWebhook tags: @@ -3618,27 +3633,29 @@ webhooks: balanceDecrease: summary: A transaction just cleared a customer account and the balance has decreased value: - account: - accountId: Account:019542f5-b3e7-1d02-0000-000000000005 - oldBalance: - amount: 50000 + id: Webhook:019542f5-b3e7-1d02-0000-000000000007 + type: ACCOUNT.BALANCE_UPDATED + timestamp: '2025-08-15T14:32:00Z' + data: + id: InternalAccount:019542f5-b3e7-1d02-0000-000000000005 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + balance: + amount: 10000 currency: code: USD name: United States Dollar symbol: $ decimals: 2 - newBalance: - amount: 10000 + oldBalance: + amount: 50000 currency: code: USD name: United States Dollar symbol: $ decimals: 2 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: 019542f5-b3e7-1d02-0000-000000000001 - timestamp: '2025-08-15T14:32:00Z' - webhookId: Webhook:019542f5-b3e7-1d02-0000-000000000007 - type: ACCOUNT_STATUS + fundingPaymentInstructions: [] + createdAt: '2025-08-01T10:00:00Z' + updatedAt: '2025-08-15T14:32:00Z' responses: '200': description: | @@ -3656,7 +3673,7 @@ webhooks: schema: $ref: '#/components/schemas/Error401' '409': - description: Conflict - Webhook has already been processed (duplicate webhookId) + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: @@ -3673,7 +3690,6 @@ components: name: X-Grid-Signature description: | Secp256r1 (P-256) asymmetric signature of the webhook payload, which can be used 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 @@ -3682,18 +3698,6 @@ components: If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. schemas: - AllErrors: - anyOf: - - $ref: '#/components/schemas/Error400' - - $ref: '#/components/schemas/Error401' - - $ref: '#/components/schemas/Error403' - - $ref: '#/components/schemas/Error404' - - $ref: '#/components/schemas/Error409' - - $ref: '#/components/schemas/Error410' - - $ref: '#/components/schemas/Error412' - - $ref: '#/components/schemas/Error424' - - $ref: '#/components/schemas/Error500' - - $ref: '#/components/schemas/Error501' CustomerInfoFieldName: type: string enum: @@ -8492,127 +8496,72 @@ components: description: A list of permissions to grant to the token items: $ref: '#/components/schemas/Permission' - IncomingPaymentWebhook: - allOf: - - $ref: '#/components/schemas/BaseWebhook' - - type: object - required: - - transaction - properties: - transaction: - $ref: '#/components/schemas/IncomingTransaction' - type: - $ref: '#/components/schemas/WebhookType' - description: Type of webhook event - example: INCOMING_PAYMENT - requestedReceiverCustomerInfoFields: - type: array - items: - $ref: '#/components/schemas/CounterpartyFieldDefinition' - description: Information required by the sender's VASP about the recipient. Platform must provide these in the 200 OK response if approving. Note that this only includes fields which Grid does not already have from initial customer registration. + WebhookType: + type: string + enum: + - OUTGOING_PAYMENT.COMPLETED + - OUTGOING_PAYMENT.FAILED + - OUTGOING_PAYMENT.REFUNDED + - INCOMING_PAYMENT.PENDING + - INCOMING_PAYMENT.COMPLETED + - INCOMING_PAYMENT.FAILED + - CUSTOMER.KYC_APPROVED + - CUSTOMER.KYC_REJECTED + - CUSTOMER.KYC_SUBMITTED + - CUSTOMER.KYC_MANUALLY_APPROVED + - CUSTOMER.KYC_MANUALLY_REJECTED + - ACCOUNT.BALANCE_UPDATED + - INVITATION.CLAIMED + - BULK_UPLOAD.COMPLETED + - BULK_UPLOAD.FAILED + - TEST + description: Type of webhook event in OBJECT.EVENT dot-notation. The part before the dot identifies the resource, the part after identifies the event. This lets consumers route purely on type without inspecting data.status. BaseWebhook: type: object required: - timestamp - id - type + - data properties: - timestamp: - type: string - format: date-time - description: ISO8601 timestamp when the webhook was sent (can be used to prevent replay attacks) - example: '2025-08-15T14:32:00Z' id: type: string description: Unique identifier for this webhook delivery (can be used for idempotency) example: Webhook:019542f5-b3e7-1d02-0000-000000000007 type: $ref: '#/components/schemas/WebhookType' - description: Type of webhook event - discriminator: - propertyName: type - mapping: - INCOMING_PAYMENT: '#/components/schemas/IncomingPaymentWebhook' - OUTGOING_PAYMENT: '#/components/schemas/OutgoingPaymentWebhook' - TEST: '#/components/schemas/TestWebhookRequest' - BULK_UPLOAD: '#/components/schemas/BulkUploadWebhookRequest' - INVITATION_CLAIMED: '#/components/schemas/InvitationClaimedWebhook' - KYC_STATUS: '#/components/schemas/KycStatusWebhook' - WebhookType: - type: string - enum: - - INCOMING_PAYMENT - - OUTGOING_PAYMENT - - TEST - - BULK_UPLOAD - - INVITATION_CLAIMED - - KYC_STATUS - - ACCOUNT_STATUS - description: Type of webhook event, used by the receiver to identify which webhook is being received - OutgoingPaymentWebhook: - allOf: - - $ref: '#/components/schemas/BaseWebhook' - - type: object - required: - - transaction - properties: - transaction: - $ref: '#/components/schemas/OutgoingTransaction' - type: - $ref: '#/components/schemas/WebhookType' - description: Type of webhook event - example: OUTGOING_PAYMENT - TestWebhookRequest: - allOf: - - $ref: '#/components/schemas/BaseWebhook' - - type: object - properties: - type: - $ref: '#/components/schemas/WebhookType' - description: Type of webhook event - example: TEST - BulkUploadWebhookRequest: - allOf: - - $ref: '#/components/schemas/BaseWebhook' - - type: object - required: - - bulkCustomerImportJob - properties: - bulkCustomerImportJob: - $ref: '#/components/schemas/BulkCustomerImportJob' - type: - $ref: '#/components/schemas/WebhookType' - description: Type of webhook event - example: BULK_UPLOAD - InvitationClaimedWebhook: + description: Status-specific event type in OBJECT.EVENT dot-notation (e.g., OUTGOING_PAYMENT.COMPLETED) + timestamp: + type: string + format: date-time + description: ISO 8601 timestamp of when the webhook was sent + example: '2025-08-15T14:32:00Z' + data: + type: object + description: The resource object. Contains the full resource as the corresponding GET endpoint would return it. + IncomingPaymentWebhook: allOf: - $ref: '#/components/schemas/BaseWebhook' - type: object required: - - invitation + - data properties: - invitation: - $ref: '#/components/schemas/UmaInvitation' + data: + allOf: + - $ref: '#/components/schemas/IncomingTransaction' + - type: object + properties: + requestedReceiverCustomerInfoFields: + type: array + items: + $ref: '#/components/schemas/CounterpartyFieldDefinition' + description: Information required by the sender's VASP about the recipient. Platform must provide these in the 200 OK response if approving. Note that this only includes fields which Grid does not already have from initial customer registration. type: type: string enum: - - INVITATION_CLAIMED - description: Type of webhook event - example: INVITATION_CLAIMED - KycStatusWebhook: - allOf: - - $ref: '#/components/schemas/BaseWebhook' - - type: object - required: - - customerId - - kycStatus - properties: - customerId: - type: string - description: System generated id of the customer - example: Customer:019542f5-b3e7-1d02-0000-000000000001 - kycStatus: - $ref: '#/components/schemas/KycStatus' + - INCOMING_PAYMENT.PENDING + - INCOMING_PAYMENT.COMPLETED + - INCOMING_PAYMENT.FAILED IncomingPaymentWebhookResponse: type: object properties: @@ -8642,25 +8591,90 @@ components: example: - TAX_ID - REGISTRATION_NUMBER - AccountStatusWebhook: + OutgoingPaymentWebhook: allOf: - $ref: '#/components/schemas/BaseWebhook' - type: object required: - - accountId + - data properties: - accountId: + data: + $ref: '#/components/schemas/OutgoingTransaction' + type: type: string - description: The id of the account whose balance has changed - oldBalance: - $ref: '#/components/schemas/CurrencyAmount' - newBalance: - $ref: '#/components/schemas/CurrencyAmount' - customerId: + enum: + - OUTGOING_PAYMENT.COMPLETED + - OUTGOING_PAYMENT.FAILED + - OUTGOING_PAYMENT.REFUNDED + TestWebhookRequest: + allOf: + - $ref: '#/components/schemas/BaseWebhook' + - type: object + properties: + type: type: string - description: The ID of the customer associated with the internal account - example: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: + enum: + - TEST + BulkUploadWebhook: + allOf: + - $ref: '#/components/schemas/BaseWebhook' + - type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/BulkCustomerImportJob' + type: + type: string + enum: + - BULK_UPLOAD.COMPLETED + - BULK_UPLOAD.FAILED + InvitationClaimedWebhook: + allOf: + - $ref: '#/components/schemas/BaseWebhook' + - type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/UmaInvitation' + type: type: string - description: The ID of the customer as associated in your platform - example: 019542f5-b3e7-1d02-0000-000000000001 + enum: + - INVITATION.CLAIMED + CustomerKycWebhook: + allOf: + - $ref: '#/components/schemas/BaseWebhook' + - type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/IndividualCustomer' + type: + type: string + enum: + - CUSTOMER.KYC_APPROVED + - CUSTOMER.KYC_REJECTED + - CUSTOMER.KYC_SUBMITTED + - CUSTOMER.KYC_MANUALLY_APPROVED + - CUSTOMER.KYC_MANUALLY_REJECTED + AccountStatusWebhook: + allOf: + - $ref: '#/components/schemas/BaseWebhook' + - type: object + required: + - data + properties: + data: + allOf: + - $ref: '#/components/schemas/InternalAccount' + - type: object + properties: + oldBalance: + $ref: '#/components/schemas/CurrencyAmount' + description: The account balance before the change + type: + type: string + enum: + - ACCOUNT.BALANCE_UPDATED diff --git a/openapi/components/schemas/customers/BulkUploadWebhookRequest.yaml b/openapi/components/schemas/customers/BulkUploadWebhookRequest.yaml deleted file mode 100644 index 712b43bc..00000000 --- a/openapi/components/schemas/customers/BulkUploadWebhookRequest.yaml +++ /dev/null @@ -1,12 +0,0 @@ -allOf: - - $ref: ../webhooks/BaseWebhook.yaml - - type: object - required: - - bulkCustomerImportJob - properties: - bulkCustomerImportJob: - $ref: ./BulkCustomerImportJob.yaml - type: - $ref: ../webhooks/WebhookType.yaml - description: Type of webhook event - example: BULK_UPLOAD diff --git a/openapi/components/schemas/webhooks/AccountStatusWebhook.yaml b/openapi/components/schemas/webhooks/AccountStatusWebhook.yaml index 522e83ce..9d6fc7c4 100644 --- a/openapi/components/schemas/webhooks/AccountStatusWebhook.yaml +++ b/openapi/components/schemas/webhooks/AccountStatusWebhook.yaml @@ -2,20 +2,17 @@ allOf: - $ref: ./BaseWebhook.yaml - type: object required: - - accountId + - data properties: - accountId: + data: + allOf: + - $ref: ../customers/InternalAccount.yaml + - type: object + properties: + oldBalance: + $ref: ../common/CurrencyAmount.yaml + description: The account balance before the change + type: type: string - description: The id of the account whose balance has changed - oldBalance: - $ref: ../common/CurrencyAmount.yaml - newBalance: - $ref: ../common/CurrencyAmount.yaml - customerId: - type: string - description: The ID of the customer associated with the internal account - example: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: - type: string - description: The ID of the customer as associated in your platform - example: 019542f5-b3e7-1d02-0000-000000000001 + enum: + - ACCOUNT.BALANCE_UPDATED diff --git a/openapi/components/schemas/webhooks/BaseWebhook.yaml b/openapi/components/schemas/webhooks/BaseWebhook.yaml index 7ba29727..17a3740f 100644 --- a/openapi/components/schemas/webhooks/BaseWebhook.yaml +++ b/openapi/components/schemas/webhooks/BaseWebhook.yaml @@ -3,14 +3,8 @@ required: - timestamp - id - type + - data properties: - timestamp: - type: string - format: date-time - description: >- - ISO8601 timestamp when the webhook was sent (can be used to - prevent replay attacks) - example: '2025-08-15T14:32:00Z' id: type: string description: >- @@ -19,13 +13,17 @@ properties: example: Webhook:019542f5-b3e7-1d02-0000-000000000007 type: $ref: ./WebhookType.yaml - description: Type of webhook event -discriminator: - propertyName: type - mapping: - INCOMING_PAYMENT: ./IncomingPaymentWebhook.yaml - OUTGOING_PAYMENT: ./OutgoingPaymentWebhook.yaml - TEST: ./TestWebhookRequest.yaml - BULK_UPLOAD: ../customers/BulkUploadWebhookRequest.yaml - INVITATION_CLAIMED: ./InvitationClaimedWebhook.yaml - KYC_STATUS: ./KycStatusWebhook.yaml + description: >- + Status-specific event type in OBJECT.EVENT dot-notation + (e.g., OUTGOING_PAYMENT.COMPLETED) + timestamp: + type: string + format: date-time + description: >- + ISO 8601 timestamp of when the webhook was sent + example: '2025-08-15T14:32:00Z' + data: + type: object + description: >- + The resource object. Contains the full resource as the corresponding + GET endpoint would return it. diff --git a/openapi/components/schemas/webhooks/BulkUploadWebhook.yaml b/openapi/components/schemas/webhooks/BulkUploadWebhook.yaml new file mode 100644 index 00000000..04463232 --- /dev/null +++ b/openapi/components/schemas/webhooks/BulkUploadWebhook.yaml @@ -0,0 +1,13 @@ +allOf: + - $ref: ./BaseWebhook.yaml + - type: object + required: + - data + properties: + data: + $ref: ../customers/BulkCustomerImportJob.yaml + type: + type: string + enum: + - BULK_UPLOAD.COMPLETED + - BULK_UPLOAD.FAILED diff --git a/openapi/components/schemas/webhooks/CustomerKycWebhook.yaml b/openapi/components/schemas/webhooks/CustomerKycWebhook.yaml new file mode 100644 index 00000000..4b7c86cc --- /dev/null +++ b/openapi/components/schemas/webhooks/CustomerKycWebhook.yaml @@ -0,0 +1,16 @@ +allOf: + - $ref: ./BaseWebhook.yaml + - type: object + required: + - data + properties: + data: + $ref: ../customers/IndividualCustomer.yaml + type: + type: string + enum: + - CUSTOMER.KYC_APPROVED + - CUSTOMER.KYC_REJECTED + - CUSTOMER.KYC_SUBMITTED + - CUSTOMER.KYC_MANUALLY_APPROVED + - CUSTOMER.KYC_MANUALLY_REJECTED diff --git a/openapi/components/schemas/webhooks/IncomingPaymentWebhook.yaml b/openapi/components/schemas/webhooks/IncomingPaymentWebhook.yaml index df21f150..db3ef600 100644 --- a/openapi/components/schemas/webhooks/IncomingPaymentWebhook.yaml +++ b/openapi/components/schemas/webhooks/IncomingPaymentWebhook.yaml @@ -2,20 +2,25 @@ allOf: - $ref: ./BaseWebhook.yaml - type: object required: - - transaction + - data properties: - transaction: - $ref: ../transactions/IncomingTransaction.yaml + data: + allOf: + - $ref: ../transactions/IncomingTransaction.yaml + - type: object + properties: + requestedReceiverCustomerInfoFields: + type: array + items: + $ref: ../common/CounterpartyFieldDefinition.yaml + description: >- + Information required by the sender's VASP about the recipient. + Platform must provide these in the 200 OK response if approving. + Note that this only includes fields which Grid does not + already have from initial customer registration. type: - $ref: ./WebhookType.yaml - description: Type of webhook event - example: INCOMING_PAYMENT - requestedReceiverCustomerInfoFields: - type: array - items: - $ref: ../common/CounterpartyFieldDefinition.yaml - description: >- - Information required by the sender's VASP about the recipient. - Platform must provide these in the 200 OK response if approving. - Note that this only includes fields which Grid does not - already have from initial customer registration. + type: string + enum: + - INCOMING_PAYMENT.PENDING + - INCOMING_PAYMENT.COMPLETED + - INCOMING_PAYMENT.FAILED diff --git a/openapi/components/schemas/webhooks/InvitationClaimedWebhook.yaml b/openapi/components/schemas/webhooks/InvitationClaimedWebhook.yaml index a9087f37..bc9d0a48 100644 --- a/openapi/components/schemas/webhooks/InvitationClaimedWebhook.yaml +++ b/openapi/components/schemas/webhooks/InvitationClaimedWebhook.yaml @@ -2,12 +2,11 @@ allOf: - $ref: ./BaseWebhook.yaml - type: object required: - - invitation + - data properties: - invitation: + data: $ref: ../invitations/UmaInvitation.yaml type: type: string - enum: [INVITATION_CLAIMED] - description: Type of webhook event - example: INVITATION_CLAIMED + enum: + - INVITATION.CLAIMED diff --git a/openapi/components/schemas/webhooks/KycStatusWebhook.yaml b/openapi/components/schemas/webhooks/KycStatusWebhook.yaml deleted file mode 100644 index 082e71c4..00000000 --- a/openapi/components/schemas/webhooks/KycStatusWebhook.yaml +++ /dev/null @@ -1,13 +0,0 @@ -allOf: - - $ref: ./BaseWebhook.yaml - - type: object - required: - - customerId - - kycStatus - properties: - customerId: - type: string - description: System generated id of the customer - example: Customer:019542f5-b3e7-1d02-0000-000000000001 - kycStatus: - $ref: ../customers/KycStatus.yaml diff --git a/openapi/components/schemas/webhooks/OutgoingPaymentWebhook.yaml b/openapi/components/schemas/webhooks/OutgoingPaymentWebhook.yaml index 948a828d..2aeaa59c 100644 --- a/openapi/components/schemas/webhooks/OutgoingPaymentWebhook.yaml +++ b/openapi/components/schemas/webhooks/OutgoingPaymentWebhook.yaml @@ -2,11 +2,13 @@ allOf: - $ref: ./BaseWebhook.yaml - type: object required: - - transaction + - data properties: - transaction: + data: $ref: ../transactions/OutgoingTransaction.yaml type: - $ref: ./WebhookType.yaml - description: Type of webhook event - example: OUTGOING_PAYMENT + type: string + enum: + - OUTGOING_PAYMENT.COMPLETED + - OUTGOING_PAYMENT.FAILED + - OUTGOING_PAYMENT.REFUNDED diff --git a/openapi/components/schemas/webhooks/TestWebhookRequest.yaml b/openapi/components/schemas/webhooks/TestWebhookRequest.yaml index c7282438..e3e19cbf 100644 --- a/openapi/components/schemas/webhooks/TestWebhookRequest.yaml +++ b/openapi/components/schemas/webhooks/TestWebhookRequest.yaml @@ -3,6 +3,6 @@ allOf: - type: object properties: type: - $ref: ./WebhookType.yaml - description: Type of webhook event - example: TEST + type: string + enum: + - TEST diff --git a/openapi/components/schemas/webhooks/WebhookType.yaml b/openapi/components/schemas/webhooks/WebhookType.yaml index 6ed69c8c..afc44b78 100644 --- a/openapi/components/schemas/webhooks/WebhookType.yaml +++ b/openapi/components/schemas/webhooks/WebhookType.yaml @@ -1,12 +1,22 @@ type: string enum: - - INCOMING_PAYMENT - - OUTGOING_PAYMENT + - OUTGOING_PAYMENT.COMPLETED + - OUTGOING_PAYMENT.FAILED + - OUTGOING_PAYMENT.REFUNDED + - INCOMING_PAYMENT.PENDING + - INCOMING_PAYMENT.COMPLETED + - INCOMING_PAYMENT.FAILED + - CUSTOMER.KYC_APPROVED + - CUSTOMER.KYC_REJECTED + - CUSTOMER.KYC_SUBMITTED + - CUSTOMER.KYC_MANUALLY_APPROVED + - CUSTOMER.KYC_MANUALLY_REJECTED + - ACCOUNT.BALANCE_UPDATED + - INVITATION.CLAIMED + - BULK_UPLOAD.COMPLETED + - BULK_UPLOAD.FAILED - TEST - - BULK_UPLOAD - - INVITATION_CLAIMED - - KYC_STATUS - - ACCOUNT_STATUS description: >- - Type of webhook event, used by the receiver to identify which webhook is being - received + Type of webhook event in OBJECT.EVENT dot-notation. The part before the dot + identifies the resource, the part after identifies the event. This lets + consumers route purely on type without inspecting data.status. diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 45d66a13..0382853b 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -58,7 +58,6 @@ components: Secp256r1 (P-256) asymmetric signature of the webhook payload, which can be used to verify that the webhook was sent by Grid. - To verify the signature: 1. Get the Grid public key provided to you during integration @@ -72,19 +71,6 @@ components: If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - schemas: - AllErrors: - anyOf: - - $ref: components/schemas/errors/Error400.yaml - - $ref: components/schemas/errors/Error401.yaml - - $ref: components/schemas/errors/Error403.yaml - - $ref: components/schemas/errors/Error404.yaml - - $ref: components/schemas/errors/Error409.yaml - - $ref: components/schemas/errors/Error410.yaml - - $ref: components/schemas/errors/Error412.yaml - - $ref: components/schemas/errors/Error424.yaml - - $ref: components/schemas/errors/Error500.yaml - - $ref: components/schemas/errors/Error501.yaml paths: /config: $ref: paths/platform/config.yaml @@ -130,8 +116,8 @@ paths: $ref: paths/transactions/transactions_{transactionId}_approve.yaml /transactions/{transactionId}/reject: $ref: paths/transactions/transactions_{transactionId}_reject.yaml - /webhooks/test: - $ref: paths/webhooks/webhooks_test.yaml + /sandbox/webhooks/test: + $ref: paths/sandbox/sandbox_webhooks_test.yaml /customers/bulk/csv: $ref: paths/customers/customers_bulk_csv.yaml /customers/bulk/jobs/{jobId}: @@ -168,7 +154,7 @@ webhooks: invitation-claimed: $ref: webhooks/invitation-claimed.yaml kyc-status: - $ref: webhooks/kyc-status.yaml + $ref: webhooks/customer-kyc.yaml account-status: $ref: webhooks/account-status.yaml security: diff --git a/openapi/paths/webhooks/webhooks_test.yaml b/openapi/paths/sandbox/sandbox_webhooks_test.yaml similarity index 98% rename from openapi/paths/webhooks/webhooks_test.yaml rename to openapi/paths/sandbox/sandbox_webhooks_test.yaml index 7186edc2..97f0d6e0 100644 --- a/openapi/paths/webhooks/webhooks_test.yaml +++ b/openapi/paths/sandbox/sandbox_webhooks_test.yaml @@ -3,7 +3,7 @@ post: description: Send a test webhook to the configured endpoint operationId: sendTestWebhook tags: - - Webhooks + - Sandbox security: - BasicAuth: [] responses: diff --git a/openapi/webhooks/account-status.yaml b/openapi/webhooks/account-status.yaml index 02954cf7..7be4d647 100644 --- a/openapi/webhooks/account-status.yaml +++ b/openapi/webhooks/account-status.yaml @@ -1,7 +1,7 @@ post: - summary: Account status notification webhook + summary: Account balance update webhook description: > - Webhook that is called when the balance of an account changes + Webhook that is called when the balance of an account changes. This endpoint should be implemented by clients of the Grid API. @@ -26,7 +26,7 @@ post: should be rejected. - ### Account status + ### Account balance update When the balance of an internal account changes, we will push a notification with information on the account, the new balance, and who the account belongs to. @@ -47,27 +47,29 @@ post: balanceDecrease: summary: A transaction just cleared a customer account and the balance has decreased value: - account: - accountId: Account:019542f5-b3e7-1d02-0000-000000000005 - oldBalance: - amount: 50000 + id: Webhook:019542f5-b3e7-1d02-0000-000000000007 + type: ACCOUNT.BALANCE_UPDATED + timestamp: '2025-08-15T14:32:00Z' + data: + id: InternalAccount:019542f5-b3e7-1d02-0000-000000000005 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + balance: + amount: 10000 currency: code: USD name: United States Dollar symbol: $ decimals: 2 - newBalance: - amount: 10000 + oldBalance: + amount: 50000 currency: code: USD name: United States Dollar symbol: $ decimals: 2 - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - platformCustomerId: 019542f5-b3e7-1d02-0000-000000000001 - timestamp: '2025-08-15T14:32:00Z' - webhookId: Webhook:019542f5-b3e7-1d02-0000-000000000007 - type: ACCOUNT_STATUS + fundingPaymentInstructions: [] + createdAt: '2025-08-01T10:00:00Z' + updatedAt: '2025-08-15T14:32:00Z' responses: '200': description: > @@ -85,7 +87,7 @@ post: schema: $ref: ../components/schemas/errors/Error401.yaml '409': - description: Conflict - Webhook has already been processed (duplicate webhookId) + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: diff --git a/openapi/webhooks/bulk-upload.yaml b/openapi/webhooks/bulk-upload.yaml index 81fddd3b..ccaed41f 100644 --- a/openapi/webhooks/bulk-upload.yaml +++ b/openapi/webhooks/bulk-upload.yaml @@ -38,12 +38,15 @@ post: content: application/json: schema: - $ref: '../components/schemas/customers/BulkUploadWebhookRequest.yaml' + $ref: '../components/schemas/webhooks/BulkUploadWebhook.yaml' examples: completedUpload: summary: Successful bulk upload completion value: - bulkCustomerImportJob: + id: Webhook:019542f5-b3e7-1d02-0000-000000000008 + type: BULK_UPLOAD.COMPLETED + timestamp: '2025-08-15T14:32:00Z' + data: jobId: Job:019542f5-b3e7-1d02-0000-000000000006 status: COMPLETED progress: @@ -52,13 +55,13 @@ post: successful: 5000 failed: 0 errors: [] - webhookId: Webhook:019542f5-b3e7-1d02-0000-000000000008 - type: BULK_UPLOAD - timestamp: '2025-08-15T14:32:00Z' failedUpload: summary: Failed bulk upload value: - bulkCustomerImportJob: + id: Webhook:019542f5-b3e7-1d02-0000-000000000008 + type: BULK_UPLOAD.FAILED + timestamp: '2025-08-15T14:32:00Z' + data: jobId: Job:019542f5-b3e7-1d02-0000-000000000006 status: FAILED progress: @@ -74,9 +77,6 @@ post: details: reason: missing_required_column column: umaAddress - webhookId: Webhook:019542f5-b3e7-1d02-0000-000000000008 - type: BULK_UPLOAD - timestamp: '2025-08-15T14:32:00Z' responses: '200': description: Webhook received successfully @@ -93,7 +93,7 @@ post: schema: $ref: ../components/schemas/errors/Error401.yaml '409': - description: Conflict - Webhook has already been processed (duplicate webhookId) + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: diff --git a/openapi/webhooks/kyc-status.yaml b/openapi/webhooks/customer-kyc.yaml similarity index 60% rename from openapi/webhooks/kyc-status.yaml rename to openapi/webhooks/customer-kyc.yaml index 92b243f2..1b70e7b0 100644 --- a/openapi/webhooks/kyc-status.yaml +++ b/openapi/webhooks/customer-kyc.yaml @@ -51,16 +51,51 @@ post: content: application/json: schema: - $ref: ../components/schemas/webhooks/KycStatusWebhook.yaml + $ref: ../components/schemas/webhooks/CustomerKycWebhook.yaml examples: kycApprovedWebhook: summary: When a customer KYC has been approved value: - customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 - kycStatus: APPROVED - type: KYC_STATUS + id: Webhook:019542f5-b3e7-1d02-0000-000000000007 + type: CUSTOMER.KYC_APPROVED timestamp: '2025-08-15T14:32:00Z' - webhookId: Webhook:019542f5-b3e7-1d02-0000-000000000007 + data: + id: Customer:019542f5-b3e7-1d02-0000-000000000001 + platformCustomerId: 9f84e0c2a72c4fa + customerType: INDIVIDUAL + umaAddress: $john.doe@uma.domain.com + kycStatus: APPROVED + fullName: John Michael Doe + birthDate: '1990-01-15' + nationality: US + address: + line1: 123 Main Street + line2: Apt 4B + city: San Francisco + state: CA + postalCode: '94105' + country: US + createdAt: '2025-07-21T17:32:28Z' + updatedAt: '2025-07-21T17:32:28Z' + isDeleted: false + kycRejectedWebhook: + summary: When a customer KYC has been rejected + value: + id: Webhook:019542f5-b3e7-1d02-0000-000000000008 + type: CUSTOMER.KYC_REJECTED + timestamp: '2025-08-15T14:32:00Z' + data: + id: Customer:019542f5-b3e7-1d02-0000-000000000002 + platformCustomerId: 4b7c1e9d3f5a8e2 + customerType: INDIVIDUAL + umaAddress: $jane.smith@uma.domain.com + kycStatus: REJECTED + fullName: Jane Smith + birthDate: '1988-03-22' + nationality: US + createdAt: '2025-07-21T17:32:28Z' + updatedAt: '2025-08-15T14:32:00Z' + isDeleted: false responses: '200': description: > @@ -78,7 +113,7 @@ post: schema: $ref: ../components/schemas/errors/Error401.yaml '409': - description: Conflict - Webhook has already been processed (duplicate webhookId) + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: diff --git a/openapi/webhooks/incoming-payment.yaml b/openapi/webhooks/incoming-payment.yaml index 78ad1b99..c0646b01 100644 --- a/openapi/webhooks/incoming-payment.yaml +++ b/openapi/webhooks/incoming-payment.yaml @@ -70,7 +70,10 @@ post: pendingPayment: summary: Pending payment example requiring approval value: - transaction: + id: Webhook:019542f5-b3e7-1d02-0000-000000000007 + type: INCOMING_PAYMENT.PENDING + timestamp: '2025-08-15T14:32:00Z' + data: id: Transaction:019542f5-b3e7-1d02-0000-000000000005 status: PENDING type: INCOMING @@ -85,24 +88,24 @@ post: decimals: 2 customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 platformCustomerId: 18d3e5f7b4a9c2 - reconciliationInstructions: - reference: REF-123456789 counterpartyInformation: FULL_NAME: John Sender BIRTH_DATE: '1985-06-15' NATIONALITY: US - requestedReceiverCustomerInfoFields: - - name: NATIONALITY - mandatory: true - - name: ADDRESS - mandatory: false - timestamp: '2025-08-15T14:32:00Z' - webhookId: Webhook:019542f5-b3e7-1d02-0000-000000000007 - type: INCOMING_PAYMENT + reconciliationInstructions: + reference: REF-123456789 + requestedReceiverCustomerInfoFields: + - name: NATIONALITY + mandatory: true + - name: ADDRESS + mandatory: false incomingCompletedPayment: summary: Completed payment notification value: - transaction: + id: Webhook:019542f5-b3e7-1d02-0000-000000000007 + type: INCOMING_PAYMENT.COMPLETED + timestamp: '2025-08-15T14:32:00Z' + data: id: Transaction:019542f5-b3e7-1d02-0000-000000000005 status: COMPLETED type: INCOMING @@ -122,9 +125,6 @@ post: description: Payment for services reconciliationInstructions: reference: REF-123456789 - timestamp: '2025-08-15T14:32:00Z' - webhookId: Webhook:019542f5-b3e7-1d02-0000-000000000007 - type: INCOMING_PAYMENT responses: '200': description: > @@ -174,7 +174,7 @@ post: schema: $ref: ../components/schemas/webhooks/IncomingPaymentWebhookForbiddenResponse.yaml '409': - description: Conflict - Webhook has already been processed (duplicate webhookId) + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: diff --git a/openapi/webhooks/invitation-claimed.yaml b/openapi/webhooks/invitation-claimed.yaml index da81b05a..0f67ed9e 100644 --- a/openapi/webhooks/invitation-claimed.yaml +++ b/openapi/webhooks/invitation-claimed.yaml @@ -60,7 +60,10 @@ post: claimedInvitation: summary: Invitation claimed notification value: - invitation: + id: Webhook:019542f5-b3e7-1d02-0000-000000000008 + type: INVITATION.CLAIMED + timestamp: '2025-09-01T15:45:00Z' + data: code: 019542f5 createdAt: '2025-09-01T14:30:00Z' claimedAt: '2025-09-01T15:45:00Z' @@ -68,9 +71,6 @@ post: inviteeUma: $invitee@uma.domain status: CLAIMED url: https://uma.me/i/019542f5 - timestamp: '2025-09-01T15:45:00Z' - webhookId: Webhook:019542f5-b3e7-1d02-0000-000000000008 - type: INVITATION_CLAIMED responses: '200': description: Webhook received successfully @@ -87,7 +87,7 @@ post: schema: $ref: ../components/schemas/errors/Error401.yaml '409': - description: Conflict - Webhook has already been processed (duplicate webhookId) + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: diff --git a/openapi/webhooks/outgoing-payment.yaml b/openapi/webhooks/outgoing-payment.yaml index f9f62ec4..1bba152d 100644 --- a/openapi/webhooks/outgoing-payment.yaml +++ b/openapi/webhooks/outgoing-payment.yaml @@ -27,7 +27,7 @@ post: This webhook is informational only and is sent when an outgoing payment - completes successfully or fails. + completes successfully, fails, or is refunded. operationId: outgoingPaymentWebhook tags: - Webhooks @@ -43,7 +43,10 @@ post: outgoingCompletedPayment: summary: Completed outgoing payment value: - transaction: + id: Webhook:019542f5-b3e7-1d02-0000-000000000007 + type: OUTGOING_PAYMENT.COMPLETED + timestamp: '2025-08-15T14:32:00Z' + data: id: Transaction:019542f5-b3e7-1d02-0000-000000000005 status: COMPLETED type: OUTGOING @@ -65,30 +68,20 @@ post: decimals: 2 customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 platformCustomerId: 18d3e5f7b4a9c2 - settlementTime: '2025-08-15T14:30:00Z' - createdAt: '2025-08-15T14:25:18Z' - description: 'Payment for invoice #1234' exchangeRate: 0.92 quoteId: Quote:019542f5-b3e7-1d02-0000-000000000006 - paymentInstructions: - - accountOrWalletInfo: - reference: UMA-Q12345-REF - accountType: US_ACCOUNT - accountNumber: 987654321 - routingNumber: 123456789 - accountCategory: CHECKING - bankName: Chase Bank - - accountOrWalletInfo: - accountType: SOLANA_WALLET - assetType: USDC - address: 4Nd1m6Qkq7RfKuE5vQ9qP9Tn6H94Ueqb4xXHzsAbd8Wg - timestamp: '2025-08-15T14:32:00Z' - webhookId: Webhook:019542f5-b3e7-1d02-0000-000000000007 - type: OUTGOING_PAYMENT + settledAt: '2025-08-15T14:30:00Z' + createdAt: '2025-08-15T14:25:18Z' + description: 'Payment for invoice #1234' + paymentInstructions: [] + rateDetails: {} failedPayment: summary: Failed outgoing payment value: - transaction: + id: Webhook:019542f5-b3e7-1d02-0000-000000000007 + type: OUTGOING_PAYMENT.FAILED + timestamp: '2025-08-15T14:32:00Z' + data: id: Transaction:019542f5-b3e7-1d02-0000-000000000005 status: FAILED type: OUTGOING @@ -105,21 +98,7 @@ post: platformCustomerId: 18d3e5f7b4a9c2 createdAt: '2025-08-15T14:25:18Z' quoteId: Quote:019542f5-b3e7-1d02-0000-000000000006 - paymentInstructions: - - accountOrWalletInfo: - reference: UMA-Q12345-REF - accountType: US_ACCOUNT - accountNumber: 987654321 - routingNumber: 123456789 - accountCategory: CHECKING - bankName: Chase Bank - - accountOrWalletInfo: - accountType: SOLANA_WALLET - assetType: USDC - address: 4Nd1m6Qkq7RfKuE5vQ9qP9Tn6H94Ueqb4xXHzsAbd8Wg - timestamp: '2025-08-15T14:32:00Z' - webhookId: Webhook:019542f5-b3e7-1d02-0000-000000000007 - type: OUTGOING_PAYMENT + failureReason: INSUFFICIENT_FUNDS responses: '200': description: Webhook received successfully @@ -136,7 +115,7 @@ post: schema: $ref: ../components/schemas/errors/Error401.yaml '409': - description: Conflict - Webhook has already been processed (duplicate webhookId) + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: diff --git a/openapi/webhooks/test-webhook.yaml b/openapi/webhooks/test-webhook.yaml index 242cd832..355264f6 100644 --- a/openapi/webhooks/test-webhook.yaml +++ b/openapi/webhooks/test-webhook.yaml @@ -45,9 +45,10 @@ post: testWebhook: summary: Test webhook example value: - timestamp: '2025-08-15T14:32:00Z' - webhookId: Webhook:019542f5-b3e7-1d02-0000-000000000001 + id: Webhook:019542f5-b3e7-1d02-0000-000000000001 type: TEST + timestamp: '2025-08-15T14:32:00Z' + data: {} responses: '200': description: >- @@ -66,7 +67,7 @@ post: schema: $ref: ../components/schemas/errors/Error401.yaml '409': - description: Conflict - Webhook has already been processed (duplicate webhookId) + description: Conflict - Webhook has already been processed (duplicate id) content: application/json: schema: