RPM Webhooks

Webhooks allow third party systems to be notified when certain events happen in RPM. Whenever the event occurs an HTTP POST request is sent to a specific URL with details of what happened and which object was affected.

Setup

When setting up a new webhook provide:

  1. Name – identify the webhook
  2. Process – identify the associated process (only for process related webhooks)
  3. Url – the url to request when the event happens
  4. Event – the event to track. See the list of valid event names section
  5. Secret – secret key used to generate a checksum to allow the receiving system to confirm the request is indeed from RPM (do not publish anywhere, should only be known by RPM and the receiving system)

Event names

  • form.start – when a form is created on the associated process
  • form.edit – when a form is edited on the associated process. Including:
    • Approval status
    • Notes
    • Archive/activation
    • File attachments are modified
  • form.trash – when a form on the associated process is sent to the trash
  • form.restore – when a form on the associated process is restored from the trash
  • action.add – when an action is added to a form (this also triggers when an action is restored from the trash)
  • action.edit – when an action is edited on a form
  • action.trash – when an action is trashed
  • agency.add – when an agency is added (manually or via import)
  • agency.edit – when an agency is edited. Including when adding any of these change:
    • File attachments
    • Notes
    • Supplier exclusion
    • Archive/activate
    • Permissions
    • Staff assignments
    • It also triggers when the agency is restored from trash
  • agency.trash – when an agency is trashed
  • rep.add – when a rep is added (during agency creation and when done manually)
  • rep.edit – when a rep is modified manually or via import. Including when any of these change:
    • Files
    • Notes
    • Is promoted to manager
    • Sign in information
    • Contact information
    • When the rep is restored from the trash
  • rep.trash – when a rep is trashed
  • staff.add – when a staff user is added manually or via import
  • staff.edit – when a staff user is edited. Including when any of these change:
    • A proxy is added
    • Sign in information
    • Staff group membership
  • staff.trash – when a staff user is trashed
  • staff.restore – when a staff user is restored from the trash
  • commrun.open – when a commission run is opened. Including when a new one is started
  • commrun.closed – when a commission run is closed
  • commrun.import – when a commission import finishes
  • commrun.calculate – when a commission calculation finishes
  • file.add – when a file is added to a form
  • file.edit – when a file attached to a form is changed, including when any of these change:
    • Name
    • Note
    • Security
    • Folder
  • file.trash – when a file attached to a form is trashed
  • file.restore – when a file attached to a form is restored
  • supplier.add – when a new supplier is added
  • supplier.edit – when any of the following supplier attributes are modified:
    • Name
    • Logo
    • Primary contact
    • Basic fields
    • Bypass codes
    • Notes
    • Files
  • supplier.delete – when a supplier is deleted
  • customer.add – when a new customer is added
  • customer.edit – when any of the following customer attributes are modified:
    • Name
    • Website
    • Any contact information (first name, last name, phones, address, and email)
    • Basic fields
    • Primary location information (address or basic fields)
    • Notes
    • Files
  • customer.trash – when a customer is trashed
  • customer.restore – when a customer is restored from the trash
  • datawarehouse.start – when a data warehouse synchronization begins, use Info to get details on the status of the data warehouse
  • datawarehouse.finish – when a data warehouse synchronization finishes (successfully or not), use Info to get details on the status of the data warehouse
  • announcements.updated (since RPM33) – when an announcement is made active or stops being active. Use along with the AnnouncementsList to fetch an up-to-date list of announcements

Receiving the HTTP Request

The receiving system will receive an HTTP Request using POST. The following headers are sent with the request:

  • Content-Type – <string> the type of the POST data content (always application/json)
  • X-RPM-Instance – <string> The instance name for the sending RPM instance
  • X-RPM-InstanceID – <int> The instance ID for the sending RPM instance
  • X-RPM-Subscriber – <int> The ID of the subscriber where the event happened
  • X-RPM-Signature – <string> checksum signature to allow receiving system to validate the request
  • User-Agent – RPM-Webhook
  • Host – IP address of the RPM instance that generated the request
  • Content-Length – Length of POST data

Note: RFC-2616 states that headers are case-insensitive so it is recommended to match headers in that way.

Here’s an example request received (the JSON has been formatted for readability):

Content-Type: application/json
X-RPM-Instance: Cube11
X-RPM-InstanceID: 2001
X-RPM-Subscriber: 1071
X-RPM-Signature: 930c0cacf3866d3fa71ed5c5dbac39fe5330b4e3908039e0d689dcf8a0ffb780
User-Agent: RPM-Webhook
Host: 192.168.0.37:81
Content-Length: 124
Expect: 100-continue
Connection: Keep-Alive

{
    "ObjectID": 67346,
    "ObjectType": 520,
    "ParentID": 2011,
    "ParentType": 510,
    "EventName": "form.edit",
    "RequestID": 416,
    "StatusID": 5415
}

Validating the request

To prevent forged requests or DDoS attacks, RPM sends the X-RPM-Signature header which is the result of encrypting the request body via an HMAC encryption using SHA256 (using the Secret as the crypto key).

The receiving system should repeat the same HMAC encryption process using the same shared secret key to confirm the request is valid. If the X-RPM-Signature header is missing or the checksum does not match then the system can consider the requesting IP address for blacklisting.

Examples

PHP

$headers = [];
// make headers lowercase
foreach (getallheaders() as $name => $value) {
	$name = strtolower($name);
	$headers[$name] = $value;
}
$body    = file_get_contents('php://input');
$secret  = 'valar morghulis'; // your own secret
$hashed  = hash_hmac('sha256', $body, $secret);

if ($hashed === $headers['x-rpm-signature']) {
	echo "This request is legit";
} else {
	die("You shall not pass");
}

NodeJS

var crypto = require('crypto');
var secret = 'Alles dreht sich!'; // Your own secret

function getSignature(data, secret) {
    var hmac = crypto.createHmac('sha256', secret);
    hmac.update(typeof data === 'object' ? JSON.stringify(data) : '' + data);
    return hmac.digest('hex');
}

function validateSignature(signRecieved, data, secret) {
    var signCalculated = getSignature(data, secret);
    if (signCalculated !== signRecieved) {
        throw new Error(`Wrong signature. Calculated: ${signCalculated}, recieved: ${signRecieved}`));
    }
}

validateSignature(request.headers['x-rpm-signature'], request.body, secret);

Webhook payload content

Each webhook request will contain a JSON encoded payload that contains details of the object that was involved, depending of the event type the payload changes:

form.start, form.edit, form.trash, form.restore

{
    "ObjectID": <int>, // FormID
    "ObjectType": 520, // Form
    "ParentID": <int>, // ProcessID
    "ParentType": 510, // Process
    "EventName": <string>, // Event name
    "RequestID": <int>, // ID of the request, to match the webhook history
    "StatusID": <int> // ID of the status of the form
}

action.add, action.edit, action.trash

{
    "ObjectID": <int>, // Action ID
    "ObjectType": 525, // Action
    "ParentID": <int>, // Form ID
    "ParentType": 520, // Form
    "EventName": <string>, // Event name
    "RequestID": <int>, // ID of the request, to match the webhook history
    "StatusID": <int> // Whether the action is marked done (0 = no, 1 = yes) Currently only works on action.add and action.edit. The action.trash always reports 0
}

agency.add, agency.edit, agency.trash

{
    "ObjectID": <int>, // Agency ID
    "ObjectType": 200, // Agency
    "EventName": <string>, // Event name
    "RequestID": <int>, // ID of the request, to match the webhook history
}

rep.add, rep.edit, rep.trash

{
    "ObjectID": <int>, // Rep ID
    "ObjectType": 1, // Rep
    "EventName": <string>, // Event name
    "RequestID": <int>, // ID of the request, to match the webhook history
}

staff.add, staff.edit, staff.trash, staff.restore

{
    "ObjectID": <int>, // Staff ID
    "ObjectType": 3, // Staff
    "EventName": <string>, // Event name
    "RequestID": <int>, // ID of the request, to match the webhook history
}

commrun.calculate, commrun.open, commrun.closed, commrun.import

{
    "ObjectType": 10, // Commission run
    "EventName": <string>, // Event name
    "RequestID": <int>, // ID of the request, to match the webhook history
    "CommRunYM": <string>, // Run year and month (e.g. 201811)
    "CommRunOpen": <bool>, // Is the run open or not (commrun.open will report false)
    "CommRunStaffOnly": <bool> // Staff only or not hidden
}

Notes:

  • CommRunOpen will always be true on commrun.open webhooks
  • CommRunOpen will always be false on commrun.closed webhooks

file.add, file.edit, file.trash, file.restore

{
    "ObjectID": <int>, // File ID
    "ObjectType": 450, // File
    "ParentID": <int>, // Form ID
    "ParentType": 520, // Form
    "EventName": <string>, // Event name
    "RequestID": <int>, // ID of the request, to match the webhook history
    "StatusID": 0 // always 0
}

supplier.add, supplier.edit, supplier.delete

{
    "ObjectID": <int>, // Supplier ID
    "ObjectType": 203, // Supplier
    "EventName": <string>, // Event name
    "RequestID": <int> // ID of the request, to match the webhook history
}

customer.add, customer.edit, customer.trash, customer.restore

{
    "ObjectID": <int>, // Customer ID
    "ObjectType": 5, // Customer
    "EventName": <string>, // Event name
    "RequestID": <int> // ID of the request, to match the webhook history
}

announcements.updated

{
    "ObjectType": 33, // Announcement
    "EventName": <string>, // Event name
    "RequestID": <int> // ID of the request, to match the webhook history
}

Webhooks and API requests

RPM’s web API allows applications to add, edit, archive, activate, and trash forms, as well as adding and editing actions.
By default, these request will not trigger webhooks.

To enable API users to trigger webhooks, a new WebhookEvaluate parameter has been added to those requests.

Be conscious that you could produce cyclic requests if not careful when using WebhookEvaluate set to true.