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:
- Business created via Create a Business endpoint
- Business is subscribed to monitoring
- At some point in the future, a change to that business is detected. The business record is updated
- A webhook event is sent to registered webhook endpoint(s)
- 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 pagetype
- 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 registrations, subscribe to the registration.created
and registration.updated
events in your webhook endpoint. You can use this to detect a change to any field on an existing registration and learn about new registrations associated with a business.
The following example demonstrates handling an event update triggered by a change is registration status but the same pattern can be generalized for any field associated with a registration.
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']
# check for presence of a change in status field
if registration_event['previous_attributes').keys.include('status')
puts registration_event['status']
end
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']
if 'status' in registration_event['previous_attributes'].keys():
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 === 'registration.updated') {
const registrationEvent = event.data.object;
const businessId = registrationEvent.business_id;
if (Object.keys(registrationEvent.previous_attributes).includes('status') {
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.
A special previous_attributes
field is included in the embedded event object. This field contains a JSON object with the associated key/value pairs that have changed (and includes the previous values).
{
"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",
"previous_attributes": {
"status": "active"
}
}
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
Updated 23 days ago