Integration Guide - SOS Monitoring

In this guide you'll learn how to use Monitoring to listen for changes to SOS registrations associated with subscribed businesses on Middesk's Identity Platform.

Before getting started

  • Work with our Sales Team to enable Monitoring on your account
  • Ensure that you can authenticate against the Middesk API, create businesses and ingest Middesk results via Webhooks (Integration Guide)

Monitoring Lifecycle

Here's the typical lifecycle of a business with monitoring enabled:

  1. Business created via Create a Business endpoint
  2. Business is subscribed to monitoring
  3. At some point in the future, a change to that business is detected. The business record is updated
  4. A webhook event is sent to registered webhook endpoint(s)
  5. The client application ingests and evaluates the change in the business record

Step 1: Subscribe a business

You can enable monitoring on Middesk businesses using 4 different strategies. Work with your account manager to determine the best strategy for your specific use-case.

Automatic Monitoring Subscription

All businesses can be automatically subscribed when a Business Verification order completes.

Policy Action

Policies provides a rule-based system for automation within the Middesk platform. A custom policy Action can be used to subscribe Middesk businesses when certain conditions are met.

Via API

Use the Monitor API to create, update and delete business monitors via the Middesk REST api interface for fine-grained control within your application.

Via Dashboard

You can manually enable monitoring on a Business with a completed business verification order via the Monitoring tab.

Step 2: Set up and listen for Monitoring webhook events

Depending on the type of Monitor that you'll be listening for, you'll need to register a new webhook with the corresponding event types selected. For this example, we'll focus on handling SOS monitoring events.

Step 3: Consuming and Evaluating Monitor Events

Event Payloads

Webhook requests contain a few top level fields:

  • data - This field contains the event specific payload. This is the field we'll focus on as it contains all the information about a monitoring event. NOTE: Event payloads will represent the change identified in the business record rather than full business object. For more granular control, you can use these event payloads.
  • id - This is a Middesk generated identifier for the webhook. It is useful for debugging and can be used to search for pertinent webhook logs on the Middesk webhooks page
  • type - The type of webhook being provided.
  • account_id - The Middesk generated identifier for the account the webhook is sent for. Useful if you are routing traffic across multiple Middesk accounts

When a monitoring event is triggered, the composite business record AND business insights will be updated.

While it's possible to consume monitoring specific payloads (example payloads), for typical use-cases, it is usually sufficient to retrieve the business associated with the event. You can access the business's ID via the data.object.business_id field included in the webhook payload. Retrieve the Middesk Business using the Middesk REST API (GET https://api.middesk.com/v1/businesses/{id}). The response will include the full business record.

Use Case: Changes in Business Name

To handle changes to a business's name, subscribe to the name.created and name.deleted events in your webhook endpoint.

require 'json'

# Sinatra
post '/my/monitoring_webhook/url' do
  payload = request.body.read
  event = JSON.parse(payload)
  
  case event.type
  when 'name.created'
    name_event = event['data']['object']
    business_id = name_event['business_id']
    puts 'Business name changed!
  else
    # Unexpected event type
    status 400
    return
  end

  status 200
end
from flask import Flask, request, jsonify

app = Flask(__name__)

# Flask
@app.route('/my/monitoring_webhook/url', methods=['POST'])
def monitoring_webhook():
    payload = request.data
    event = request.get_json()

    if event['type'] == 'name.created':
        name_event = event['data']['object']
        business_id = name_event['business_id']
        print('Business name changed!')
    else:
        # Unexpected event type
        return '', 400

    return '', 200

if __name__ == '__main__':
    app.run(debug=True)
const express = require('express');
const app = express();

app.use(express.json()); // Middleware to parse JSON request bodies

// Express route
app.post('/my/monitoring_webhook/url', (req, res) => {
    const event = req.body;

    if (event.type === 'name.created') {
        const nameEvent = event.data.object;
        const businessId = nameEvent.business_id;
        console.log('Business name changed!');
    } else {
        // Unexpected event type
        return res.sendStatus(400);
    }

    res.sendStatus(200);
});

// Start the Express server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
    console.log(`Server is running on port ${PORT}`);
});

Using the event payload

Depending on your use-case, you can use the name event directly (e.g. store the new name for review in your application) or use the event as a trigger to re-evaluate the approval status of the business using the full business payload (see below).

{
  "object": "name",
  "id": "8d876707-651d-46ab-94e2-3f9062b4c0dd",
  "name": "A new state",
  "submitted": false,
  "type": null,
  "business_id": "2f30n1b4-c508-4792-9354-e59b5a9e1adf",
  "sources": [
    {
      "id": "c6f763df-494d-4446-b328-9a27484b24db",
      "type": "registration",
      "metadata": {
        "state": "MO",
        "status": "inactive",
        "file_number": "X00781734",
        "jurisdiction": "DOMESTIC"
      }
    }
  ]
}

Using business review tasks

You can re-evaluate the business's review tasks by requesting the full business payload using the Retrieve Business Endpoint. For example, you can use the Name Review Task to evaluate whether the name still matches to the submitted business name.

require 'net/http'
require 'net/https'
require 'json'

def send_request
	business_id = "<business_id>"
  uri = URI("https://api.middesk.com/v1/businesses/#{business_id}")

  # Create client
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true
  http.verify_mode = OpenSSL::SSL::VERIFY_PEER

  # Create Request
  req =  Net::HTTP::Get.new(uri)
  # Add headers
  req.add_field "Accept", "application/json"
  # Add headers
  req.add_field "Authorization", "Basic <api_key>"

  # Fetch Request
  res = http.request(req)
  
	response = JSON.parse(res.body)
	name_task = response['review']['tasks'].find { |task| task['category'] == 'name' }
	
	if name_task['status'] == 'success'
		puts 'Name match'
	else
    puts 'Name match failure'
	end
rescue StandardError => e
  puts "HTTP Request failed (#{e.message})"
end
import requests
from requests.auth import HTTPBasicAuth

def send_request():
    business_id = "<business_id>"
    url = f"https://api.middesk.com/v1/businesses/{business_id}"

    headers = {
        "Accept": "application/json",
        "Authorization": "Basic <api_key>"
    }

    try:
        # Make the request
        response = requests.get(url, headers=headers, auth=HTTPBasicAuth('<api_key>', ''))
        
        # Parse the response JSON
        response_data = response.json()
        name_task = next((task for task in response_data['review']['tasks'] if task['category'] == 'name'), None)

        if name_task and name_task['status'] == 'success':
            print('Name match')
        else:
            print('Name match failure')

    except requests.exceptions.RequestException as e:
        print(f"HTTP Request failed ({e})")

send_request()
async function sendRequest() {
  const businessId = "<business_id>";
  const url = `https://api.middesk.com/v1/businesses/${businessId}`;

  const headers = {
    "Accept": "application/json",
    "Authorization": "Basic <api_key>"
  };

  try {
    // Make the request using fetch
    const response = await fetch(url, { headers });
    
    // Check if the request was successful
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }

    const responseData = await response.json();
    const nameTask = responseData.review.tasks.find(task => task.category === 'name');

    if (nameTask && nameTask.status === 'success') {
      console.log('Name match');
    } else {
      console.log('Name match failure');
    }
  } catch (error) {
    console.error(`HTTP Request failed: ${error.message}`);
  }
}

sendRequest();

Use Case: Changes in People associated with Business

You can monitor for changes to the people associated with your monitored business and use business review tasks to confirm that the submitted persons continue to match to the officers associated with the business.

To handle changes to a business's name, subscribe to the person.created and person.deleted events in your webhook endpoint.

require 'json'

# Sinatra
post '/my/monitoring_webhook/url' do
  payload = request.body.read
  event = JSON.parse(payload)
  
  case event.type
  when 'person.created'
    person_event = event['data']['object']
    business_id = person_event['business_id']
    puts 'Person added!
  else
    # Unexpected event type
    status 400
    return
  end

  status 200
end
from flask import Flask, request, jsonify

app = Flask(__name__)

# Flask
@app.route('/my/monitoring_webhook/url', methods=['POST'])
def monitoring_webhook():
    payload = request.data
    event = request.get_json()

    if event['type'] == 'person.created':
        person_event = event['data']['object']
        business_id = person_event['business_id']
        puts 'Person added!
    else:
        # Unexpected event type
        return '', 400

    return '', 200

if __name__ == '__main__':
    app.run(debug=True)
const express = require('express');
const app = express();

app.use(express.json()); // Middleware to parse JSON request bodies

// Express route
app.post('/my/monitoring_webhook/url', (req, res) => {
    const event = req.body;

    if (event.type === 'person.created') {
        const personEvent = event.data.object;
        const businessId = personEvent.business_id;
        console.log('Business name changed!');
    } else {
        // Unexpected event type
        return res.sendStatus(400);
    }

    res.sendStatus(200);
});

// Start the Express server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
    console.log(`Server is running on port ${PORT}`);
});

Using the event payload

Depending on your use-case, you can use the name event directly (e.g. store the new name for review in your application) or use the event as a trigger to re-evaluate the approval status of the business using the full business payload (see below).

{
  "object": "person",
  "name": "JOHN DOE",
  "submitted": false,
  "business_id": "628821e0-3a16-4d84-8c7b-1e8f5920ac65",
  "sources": [
    {
      "id": "f7cb73f4-gcaf-4676-92c5-396ec3fa7694",
      "type": "registration",
      "metadata": {
        "state": "MT",
        "status": "active",
        "file_number": "F0012345",
        "jurisdiction": "FOREIGN"
      }
    }
  ],
  "titles": [
    {
      "object": "person_title",
      "title": "DIRECTOR"
    }
  ],
  "people_bankruptcies": []
}

Using the business review tasks

You can re-evaluate the business's review tasks by requesting the full business payload using the Retrieve Business Endpoint. You can use the Person Verification Task to evaluate whether the name still matches to the submitted business name.

require 'net/http'
require 'net/https'
require 'json'

def send_request
	business_id = "<business_id>"
  uri = URI("https://api.middesk.com/v1/businesses/#{business_id}")

  # Create client
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true
  http.verify_mode = OpenSSL::SSL::VERIFY_PEER

  # Create Request
  req =  Net::HTTP::Get.new(uri)
  # Add headers
  req.add_field "Accept", "application/json"
  # Add headers
  req.add_field "Authorization", "Basic <api_key>"

  # Fetch Request
  res = http.request(req)
  
	response = JSON.parse(res.body)
	person_task = response['review']['tasks'].find { |task| task['category'] == 'person_verification' }
	
	if person_task['status'] == 'success'
		puts 'Person match'
	else
    puts 'Person match failure'
	end
rescue StandardError => e
  puts "HTTP Request failed (#{e.message})"
end
import requests
from requests.auth import HTTPBasicAuth

def send_request():
    business_id = "<business_id>"
    url = f"https://api.middesk.com/v1/businesses/{business_id}"

    headers = {
        "Accept": "application/json",
        "Authorization": "Basic <api_key>"
    }

    try:
        # Make the request
        response = requests.get(url, headers=headers, auth=HTTPBasicAuth('<api_key>', ''))
        
        # Parse the response JSON
        response_data = response.json()
        person_task = next((task for task in response_data['review']['tasks'] if task['category'] == 'person_verification'), None)

        if person_task and person_task['status'] == 'success':
            print('Person match')
        else:
            print('Person match failure')

    except requests.exceptions.RequestException as e:
        print(f"HTTP Request failed ({e})")

send_request()
  const businessId = "<business_id>";
  const url = `https://api.middesk.com/v1/businesses/${businessId}`;

  const headers = {
    "Accept": "application/json",
    "Authorization": "Basic <api_key>"
  };

  try {
    // Make the request using fetch
    const response = await fetch(url, { headers });
    
    // Check if the request was successful
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }

    const responseData = await response.json();
    const personTask = responseData.review.tasks.find(task => task.category === 'person_verification');

    if (personTask && personTask.status === 'success') {
      console.log('Person match');
    } else {
      console.log('Person match failure');
    }
  } catch (error) {
    console.error(`HTTP Request failed: ${error.message}`);
  }
}

sendRequest();

Use Case: Changes in Registration Status

You can monitor for changes to the registration(s) associated with the business you're monitoring. In this example, let's focus on checking that the domestic registration status has remained in a status of active.

To handle changes to a business's name, subscribe to the registration.created and registration.updated events in your webhook endpoint.

require 'json'

# Sinatra
post '/my/monitoring_webhook/url' do
  payload = request.body.read
  event = JSON.parse(payload)
  
  case event.type
  when 'registration.updated'
    registration_event = event['data']['object']
    business_id = registration_event['business_id']
    puts registration_event['status']
  else
    # Unexpected event type
    status 400
    return
  end

  status 200
end
from flask import Flask, request, jsonify

app = Flask(__name__)

# Flask
@app.route('/my/monitoring_webhook/url', methods=['POST'])
def monitoring_webhook():
    payload = request.data
    event = request.get_json()

    if event['type'] == 'person.created':
        registration_event = event['data']['object']
        business_id = registration_event['business_id']
        puts registration_event['status']
    else:
        # Unexpected event type
        return '', 400

    return '', 200

if __name__ == '__main__':
    app.run(debug=True)
const express = require('express');
const app = express();

app.use(express.json()); // Middleware to parse JSON request bodies

// Express route
app.post('/my/monitoring_webhook/url', (req, res) => {
    const event = req.body;

    if (event.type === 'name.created') {
        const registrationEvent = event.data.object;
        const businessId = registrationEvent.business_id;
        console.log(registrationEvent['status']);
    } else {
        // Unexpected event type
        return res.sendStatus(400);
    }

    res.sendStatus(200);
});

// Start the Express server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
    console.log(`Server is running on port ${PORT}`);
});

Using the event payload

The event payload will include a snapshot of the registration associated to the business. You can use the data.object.status field inside the webhook payload to read the registration's status directly from the webhook event.

{
  "object": "registration",
  "id": "87b564c8-fb5d-40m2-9e61-b5b5df902aeb",
  "business_id": "006ecba9-4ec4-4610-8a2a-4ff0bb101e94",
  "name": "My Monitored Business",
  "status": "inactive",
  "sub_status": null,
  "status_details": "Inactive",
  "jurisdiction": "FOREIGN",
  "entity_type": "UNKNOWN",
  "file_number": "06717223",
  "addresses": [
    "85 2nd St San Francisco CA, 94105"
  ],
  "officers": [],
  "registered_agent": {},
  "registration_date": "2007-08-21",
  "state": "KY",
  "source": "https://web.sos.ky.gov/BusSearchNProfile/search.aspx"
}

Using business review tasks

You can re-evaluate the business's review tasks by requesting the full business payload using the Retrieve Business Endpoint. Use the SOS Domestic review task to re-evaluate the domestic filing status of the business after receiving the registration.updated event.

require 'net/http'
require 'net/https'
require 'json'

def send_request
	business_id = "<business_id>"
  uri = URI("https://api.middesk.com/v1/businesses/#{business_id}")

  # Create client
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true
  http.verify_mode = OpenSSL::SSL::VERIFY_PEER

  # Create Request
  req =  Net::HTTP::Get.new(uri)
  # Add headers
  req.add_field "Accept", "application/json"
  # Add headers
  req.add_field "Authorization", "Basic <api_key>"

  # Fetch Request
  res = http.request(req)
  
	response = JSON.parse(res.body)
	sos_domestic_task = response['review']['tasks'].find { |task| task['category'] == 'sos_domestic' }
	
	if sos_domestic_task['status'] == 'success'
		puts 'SOS status active'
	else
    puts 'SOS status inactive'
	end
rescue StandardError => e
  puts "HTTP Request failed (#{e.message})"
end
import requests
from requests.auth import HTTPBasicAuth

def send_request():
    business_id = "<business_id>"
    url = f"https://api.middesk.com/v1/businesses/{business_id}"

    headers = {
        "Accept": "application/json",
        "Authorization": "Basic <api_key>"
    }

    try:
        # Make the request
        response = requests.get(url, headers=headers, auth=HTTPBasicAuth('<api_key>', ''))
        
        # Parse the response JSON
        response_data = response.json()
        sos_domestic_task = next((task for task in response_data['review']['tasks'] if task['category'] == 'sos_domestic'), None)

        if sos_domestic_task and sos_domestic_task['status'] == 'success':
            print('Name match')
        else:
            print('Name match failure')

    except requests.exceptions.RequestException as e:
        print(f"HTTP Request failed ({e})")

send_request()
  const businessId = "<business_id>";
  const url = `https://api.middesk.com/v1/businesses/${businessId}`;

  const headers = {
    "Accept": "application/json",
    "Authorization": "Basic <api_key>"
  };

  try {
    // Make the request using fetch
    const response = await fetch(url, { headers });
    
    // Check if the request was successful
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }

    const responseData = await response.json();
    const sosDomesticTask = responseData.review.tasks.find(task => task.category === 'sos_domestic');

    if (personTask && personTask.status === 'success') {
      console.log('SOS status active');
    } else {
      console.log('SOS status inactive');
    }
  } catch (error) {
    console.error(`HTTP Request failed: ${error.message}`);
  }
}

sendRequest();

Handling state specific alerts

The most recent snapshot of the registration is included in the registration.updated event. You can use this snapshot to selectively handle webhook events. For example, if you are only interested in events eminating from a specific registration state, you can choose to ignore events triggered from other registration updates.

In the example below, you can filter on KY only updates.

require 'json'

# Only handle updates in KY
post '/my/monitoring_webhook/url' do
  payload = request.body.read
  event = JSON.parse(payload)
  
  case event.type
  when 'registration.updated'
    registration_event = event['data']['object']
    business_id = registration_event['business_id']
		state = registration_event['state']
	
		# ignore other states
    return if state != 'KY'

    puts registration_event['status']
  else
    # Unexpected event type
    status 400
    return
  end

  status 200
end

Subsequently, you can use the Retrieve Business Endpoint to fetch the full registration payload and select for individual registrations.

require 'net/http'
require 'net/https'
require 'json'

def send_request
	business_id = "<business_id>"
  uri = URI("https://api.middesk.com/v1/businesses/#{business_id}")

  # Create client
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true
  http.verify_mode = OpenSSL::SSL::VERIFY_PEER

  # Create Request
  req =  Net::HTTP::Get.new(uri)
  # Add headers
  req.add_field "Accept", "application/json"
  # Add headers
  req.add_field "Authorization", "Basic <api_key>"

  # Fetch Request
  res = http.request(req)
  
	response = JSON.parse(res.body)
	ky_registration = response['registrations'].find {|registration| registration['state'] = 'KY' }
	
	if ky_registration
		puts 'KY registration"
	else
    puts 'Missing KY registration'
	end
rescue StandardError => e
  puts "HTTP Request failed (#{e.message})"
end