Fincura Webhooks API

## Supported Events The following are events you can listen to via webhooks. | Event Type      | Description | | --------------------------------------------- | -------------------------- | | GlobalCashflow.Saved | A global cashflow analysis has been saved by the user | | Analysis.Saved | An analysis has been saved by the user | | DocumentFile.Received | File saved/received to the Fincura Platform | | DocumentFile.Processing | File is being processed by the Fincura Pipeline | | DocumentFile.HumanRequired | Human Intervention is required for file | | DocumentFile.SpreadComplete | File data spread to a template (data now normalized/standardized) | | DocumentFile.Error | Error processing DocumentFile | | BulkFile.Received | Bulk File saved/received to the Fincura Platform | | BulkFile.Processing | Bulk File is being processed by the Fincura Pipeline | | BulkFile.Processed | Bulk File has completed processing | | BulkFile.Error | Error processing BulkFile | | * | Wildcard for all events | | Template.Changed | A change was detected in a template for spreading, global cashflow, or DSCR analysis | | Borrower.CreatedFromImport | A Borrower was created from processing a file import | | CalculatedStatement.Update | A calculated statement has been updated by the user | | ForecastedStatement.Update | A forecasted statement has been updated by the user | | AnnualizedStatement.Update | An annualized statement has been updated by the user | ## Webhook Request Body The webhook POST request body will contain the following json parameters: - `event` the event that triggered the callback (e.g. `DocumentFile.Received`) - `transaction_id` a unique UUID representing the event - `payload` additional event specific parameters ## Webhook Signatures All of our outgoing webhook requests contain a `X-Fincura-Signature` header which has been signed by a tenant specific key. This allows a 3rd party to validate requests are from Fincura's servers. The header contains a list of comma seperated key value pairs: - `t` - UTC timestamp of the request - `uuid` - the UUID of the webhook record - `v1` - signature of the message [(HMAC_SHA256)](https://en.wikipedia.org/wiki/HMAC) ### Obtaining the Tenant Signing Key See: [Get Tenant Settings](#operation/readTenantSettings) which returns a `webhook_signing_key` ### Calculating the Message Signature The webhook message signature is calculated by joining the `timestamp` of the request, the `uuid` of the webhook and the `body` of the request with periods (`.`) into a single string. The signature is generated by creating a HMAC SHA256 signature of that message using the tenant signing key. The calculated signature can then be matched to the `v1` signature in the `X-Fincura-Signature` header to verify the request originated from Fincura and the body has not been tampered with. NOTE: Fincura periodically rotates signing keys for security purposes, if a signature mismatch if found, it is recommended to make sure you are using the latest signing key. Python example of validating a signature: ``` #!/usr/bin/env python import hmac import hashlib SIGNING_KEY = "tenant signing key" # Obtained via API call to `operation/readTenantSettings` def create_message(timestamp, uuid, body): return f"{timestamp}.{uuid}.{body}" def create_sha256_signature(key, message): message = message.encode() return hmac.new(key.encode('utf-8'), message, hashlib.sha256).hexdigest().upper() def verify_fincura_header(signature_header, request_body): header_params = {kv[0]:kv[1] for kv in param.split('=') for param in signature_header.split(',')} message = create_message(header_params['t'], header_params['uuid'], request_body) signature = create_sha256_signature(SIGNING_KEY, message) if signature != header_params['v1']: raise Exception('Webhook Signature Mismatch') // for example (signature shortened for brevity) header_value = 't=1602104227,uuid=5262fc90-eb99-4bd8-8712-ae50fc71ea92,v1=E9397...9C3332' request_body = '{"event:"value"}' verify_fincura_header(header_value, request_body) ```

OpenAPI Specification

fincura-webhooks-api-openapi.yml Raw ↑
openapi: 3.0.2
info:
  title: Automated Spreading and Analysis Api-Key Webhooks API
  version: 1.3.1
  description: "This [REST API](https://en.wikipedia.org/wiki/Representational_state_transfer) allows you to interact with the Automated Spreading processing and insights engine. \n\n# Authentication\n\nThis API uses API keys generated from a Automated Spreading User account. To get access to your User account, speak with your Automated Spreading account manager.\n\n# Accepted Media Types\n\n| File Type      | Extension(s)     | Content-Type(s) |\n| -------------------------------- |-------------- | ------------ |\n| PDF File | .pdf | `application/pdf` , `application/x-pdf` |\n| Excel File | .xls | `application/vnd.ms-excel`  |\n| Excel File | .xlsx | `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`  |\n| Excel File | .xlsm | `application/vnd.ms-excel.sheet.macroEnabled.12`  |\n| PNG Image | .png | `image/png`  |\n| GIF Image | .gif | `image/gif`  |\n| JPG Image | .jpg, .jpeg | `image/jpeg`  |\n| GIF Image | .gif | `image/gif`  |\n| JSON File | .json | `application/json`  |\n\n\n# Getting Started\n\n1. [Create a Borrower](#operation/createBorrower) \n2. [Add a file](#operation/createDocumentFile) for that Borrower.\n3. Analyze in the Automated Spreading App\n\n****"
  x-logo:
    url: https://manual-public-web-static-resources.s3.amazonaws.com/public-api/Moodys-icon.svg
  contact:
    name: Moody's Automated Spreading Customer Support
    email: MA_NMR_Support@moodys.com
security:
- API_Key: []
tags:
- name: Webhooks
  description: "## Supported Events\n\nThe following are events you can listen to via webhooks.\n\n| Event Type      | Description     | \n| --------------------------------------------- | -------------------------- |\n| GlobalCashflow.Saved | A global cashflow analysis has been saved by the user | \n| Analysis.Saved | An analysis has been saved by the user | \n| DocumentFile.Received | File saved/received to the Fincura Platform | \n| DocumentFile.Processing | File is being processed by the Fincura Pipeline | \n| DocumentFile.HumanRequired | Human Intervention is required for file | \n| DocumentFile.SpreadComplete | File data spread to a template (data now normalized/standardized) | \n| DocumentFile.Error | Error processing DocumentFile | \n| BulkFile.Received | Bulk File saved/received to the Fincura Platform | \n| BulkFile.Processing | Bulk File is being processed by the Fincura Pipeline | \n| BulkFile.Processed | Bulk File has completed processing | \n| BulkFile.Error | Error processing BulkFile | \n| * | Wildcard for all events | \n| Template.Changed | A change was detected in a template for spreading, global cashflow, or DSCR analysis | \n| Borrower.CreatedFromImport | A Borrower was created from processing a file import | \n| CalculatedStatement.Update | A calculated statement has been updated by the user | \n| ForecastedStatement.Update | A forecasted statement has been updated by the user | \n| AnnualizedStatement.Update | An annualized statement has been updated by the user | \n\n\n## Webhook Request Body\n\nThe webhook POST request body will contain the following json parameters:\n\n- `event` the event that triggered the callback (e.g. `DocumentFile.Received`)\n- `transaction_id` a unique UUID representing the event\n- `payload` additional event specific parameters\n\n## Webhook Signatures\n\nAll of our outgoing webhook requests contain a `X-Fincura-Signature` header which has been signed by a tenant specific key. This allows a 3rd party to validate requests are from Fincura's servers.\n\nThe header contains a list of comma seperated key value pairs:\n\n- `t` - UTC timestamp of the request\n- `uuid` - the UUID of the webhook record\n- `v1` - signature of the message [(HMAC_SHA256)](https://en.wikipedia.org/wiki/HMAC)\n\n### Obtaining the Tenant Signing Key\n\nSee: [Get Tenant Settings](#operation/readTenantSettings) which returns a `webhook_signing_key`\n\n### Calculating the Message Signature\n\nThe webhook message signature is calculated by joining the `timestamp` of the request, the `uuid` of the webhook and the `body` of the request with periods (`.`) into a single string.\n\nThe signature is generated by creating a HMAC SHA256 signature of that message using the tenant signing key.\n\nThe calculated signature can then be matched to the `v1` signature in the `X-Fincura-Signature` header to verify the request originated from Fincura and the body has not been tampered with.\n\nNOTE: Fincura periodically rotates signing keys for security purposes, if a signature mismatch if found, it is recommended to make sure you are using the latest signing key.\n\nPython example of validating a signature:\n```\n#!/usr/bin/env python\nimport hmac\nimport hashlib \n\nSIGNING_KEY = \"tenant signing key\" # Obtained via API call to `operation/readTenantSettings`\n\ndef create_message(timestamp, uuid, body):\n    return  f\"{timestamp}.{uuid}.{body}\"\n\ndef create_sha256_signature(key, message):\n    message = message.encode()\n    return hmac.new(key.encode('utf-8'), message, hashlib.sha256).hexdigest().upper()\n\ndef verify_fincura_header(signature_header, request_body):\n    header_params = {kv[0]:kv[1] for kv in param.split('=') for param in signature_header.split(',')} \n    message = create_message(header_params['t'], header_params['uuid'], request_body)\n    signature = create_sha256_signature(SIGNING_KEY, message)\n\n    if signature != header_params['v1']:\n        raise Exception('Webhook Signature Mismatch')\n\n// for example (signature shortened for brevity)\nheader_value = 't=1602104227,uuid=5262fc90-eb99-4bd8-8712-ae50fc71ea92,v1=E9397...9C3332'\nrequest_body = '{\"event:\"value\"}'\n\nverify_fincura_header(header_value, request_body)\n```"
paths:
  /v1/webhook:
    get:
      operationId: listWebhooks
      description: List registered webhooks
      parameters:
      - name: limit
        required: false
        in: query
        description: Number of results to return per page.
        schema:
          type: integer
      - name: offset
        required: false
        in: query
        description: The initial index from which to return the results.
        schema:
          type: integer
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
                required:
                - count
                - results
                properties:
                  count:
                    type: integer
                    example: 123
                  next:
                    type: string
                    nullable: true
                    format: uri
                    example: http://api.example.org/accounts/?offset=400&limit=100
                  previous:
                    type: string
                    nullable: true
                    format: uri
                    example: http://api.example.org/accounts/?offset=200&limit=100
                  results:
                    type: array
                    items:
                      $ref: '#/components/schemas/Webhook'
          description: ''
      tags:
      - Webhooks
      summary: List webhooks
    post:
      operationId: createWebhook
      description: Register a webhook. This call in idempotent.
      parameters: []
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Webhook'
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/Webhook'
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/Webhook'
      responses:
        '201':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Webhook'
          description: ''
      tags:
      - Webhooks
      summary: Create a webhook
  /v1/webhook/{uuid}:
    get:
      operationId: retrieveWebhook
      description: Retrieve a registerted webhook
      parameters:
      - name: uuid
        in: path
        required: true
        description: ''
        schema:
          type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Webhook'
          description: ''
      tags:
      - Webhooks
      summary: Get webhook info
    delete:
      operationId: destroyWebhook
      description: Delete a webhook from Fincura.
      parameters:
      - name: uuid
        in: path
        required: true
        description: ''
        schema:
          type: string
      responses:
        '204':
          description: ''
      tags:
      - Webhooks
      summary: Delete a webhook
components:
  schemas:
    Webhook:
      type: object
      properties:
        uuid:
          type: string
          format: uuid
          readOnly: true
        event_type:
          enum:
          - GlobalCashflow.Saved
          - Analysis.Saved
          - DocumentFile.Received
          - DocumentFile.Processing
          - DocumentFile.HumanRequired
          - DocumentFile.SpreadComplete
          - DocumentFile.Error
          - BulkFile.Received
          - BulkFile.Processing
          - BulkFile.Processed
          - BulkFile.Error
          - '*'
          - Template.Changed
          - Borrower.CreatedFromImport
          - CalculatedStatement.Update
          - ForecastedStatement.Update
          - AnnualizedStatement.Update
          type: string
        webhook_url:
          type: string
          format: uri
          maxLength: 2500
          pattern: "^(?:[a-z0-9.+-]*)://(?:[^\\s:@/]+(?::[^\\s:@/]*)?@)?(?:(?:0|25[0-5]|2[0-4][0-9]|1[0-9]?[0-9]?|[1-9][0-9]?)(?:\\.(?:0|25[0-5]|2[0-4][0-9]|1[0-9]?[0-9]?|[1-9][0-9]?)){3}|\\[[0-9a-f:.]+\\]|([a-z¡-\uFFFF0-9](?:[a-z¡-\uFFFF0-9-]{0,61}[a-z¡-\uFFFF0-9])?(?:\\.(?!-)[a-z¡-\uFFFF0-9-]{1,63}(?<!-))*\\.(?!-)(?:[a-z¡-\uFFFF-]{2,63}|xn--[a-z0-9]{1,59})(?<!-)\\.?|localhost))(?::[0-9]{1,5})?(?:[/?#][^\\s]*)?\\z"
        webhook_method:
          enum:
          - GET
          - POST
          type: string
          default: POST
        external_id:
          type: string
          description: External ID is used to uniquely identify a record from your system in our system. Must be unique across records.
          maxLength: 255
        signing_key:
          type: string
          readOnly: true
          maxLength: 200
      required:
      - event_type
      - webhook_url
  securitySchemes:
    API_Key:
      type: http
      scheme: bearer
      bearerFormat: JWT
x-tagGroups:
- name: Tenant
  tags:
  - Api-Key
  - TenantSettings
- name: Templates
  tags:
  - SpreadingTemplate
  - DscrTemplate
  - GlobalCashflowTemplate
- name: Borrower
  tags:
  - Borrowers
  - Files
  - Loans
  - Requirements
  - Portfolios
- name: Normalized Data
  tags:
  - DataViews
  - Templates
- name: Analysis
  tags:
  - DscrAnalysis
  - GlobalCashflowAnalysis
- name: Events
  tags:
  - Webhooks
- name: Embedded UI
  tags:
  - EmbeddedDocument
  - EmbeddedFinancials
  - EmbeddedDscrAnalysis
  - EmbeddedGlobalCashflowAnalysis