Webhooks are useful in integration scenarios when you have a separate application that needs to be informed when certain events are triggered in Aligni. Polling Aligni via the standard API endpoints is an inefficient (and in some cases ineffective) way to extract information. Properly configured and utilized webhooks provide a timely, efficient, and accurate way to synchronize an application to Aligni events.
Webhooks are currently BETA and are subject to change without notice. Nevertheless, we plan to announce changes through our API Newsletter when possible.
Managing Webhooks in Aligni
Webhooks are created and managed in the Aligni user interface. There is not presently an API available for creating, managing, or removing webhooks. The organization administrator manages webhooks in the Organization > Webhooks settings menu.
A webhook consists of the following attributes:
- Webhook URL – The complete URL to the server endpoint that will receive requests from Aligni. For example:
https://sync.mycompany.com/aligni/
- Description – An optional description of the webhook for reference.
- Associated Events – This is a list of subscribed events for the webhook. Any event in the included list will trigger a webhook request to the URL specified.
Each webhook submits a null request of event type webhook_url.ping
to your endpoint when it is created or updated. Aligni uses this ping event to test for the presence and responsiveness of your server. Your server should respond to this request with 200. See the Webhook Events section below for more information.
Webhook Signature
Each organization is assigned a unique webhook signature that is used by your client(s) to verify the authenticity of any events posted to your client. You can retrieve this signature string in the Aligni application on the Signature tab of the webhooks configuration.
You will need to share this signature with each client receiving webhooks. You can reset this at any time but will need to update each client with the new signature.
Event Log
Each webhook event is logged and can be viewed via the Webhook’s configuration page. The log displays the event details and allows the user to view the event’s request body, if applicable.
Webhook Request
The webhook request is the data sent from Aligni to your client. It is constructed as a typical web HTTP POST request and contains a request header and request body.
Event Taxonomy
Specific webhook events are referred to as event_source.event_type
such as inventory_unit.change_data_capture
.
event_source
– This generally describes the source data record of the event.event_type
– Within the event type, this is the name of the event.
Header
In order for your client to ensure our request is authentic, Aligni includes two values in the header of each webhook request:
- X-ALIGNI-SIGNATURE – A cryptographic signature of the current request used to validate the source of the request. This signature is computed over the timestamp and request body.
- X-ALIGNI-TIMESTAMP – The timestamp of the request, formatted to ISO 8601.
Body
The request body contains the event information formatted in JSON. The event information has attributes defined as follows:
id
(integer) – Unique identifier for the webhook event.event_source
(string) – Event source.event_type
(string) – Event type.url
(string) – The endpoint URL defined in the webhook configuration.action
(string) – RESTful actions that trigger events will be displayed here. For example, in the event of an inventory unit adjustment, aninventory_unit.change_data_capture
event will be triggered, where the action could equalPATCH
orPUT
.data
(JSON) – Event data payload. For contents, see the corresponding event documentation
Example Request Body
[ { "id" : "3124", "event_source" : "webhook", "event_type" : "ping", "url" : "https://sync.mycompany.com/aligni/", "action" : "PUT", "data" : [ { ... event-specific data payload ... } ] } ]
Failures
Here are some common failure conditions and their respective outcomes:
- If Aligni fails to connect to the remote client (e.g. network errors), the failure will be logged and visible in the Webhook Log in the Webhook configuration pages. Aligni will attempt to retry 25 times over the course of 21 days with increasing intervals between retries.
- Aligni ignores all responses from the client.
Authenticating a Request
To authenticate a request, your client application should perform the steps below. The intent is to prevent spoofing of erroneous requests being sent to your client that could potentially corrupt your data. Note that the examples below are shown in pseudocode but they would be representative of many common script languages such as Python or Ruby.
Step 1: Extract timestamp header from the request. It’s good to ensure this request was made recently. Aligni follows a common convention as seen in the example below, where we will decline the request if it’s older than 5 minutes.
timestamp = request.headers["X-ALIGNI-TIMESTAMP"] >>> "2020-07-28T10:41:08-11:00" if (Time.current - timestamp) > 60 * 5 return
Step 2: Concatenate the version number (“v1
“), the timestamp, and the request together using a colon separator.
basestring = "v1:" + timestamp + ":" + request_body >>> "v1:2020-07-28T10:41:08-11:00: { "id":11, "event_source":"event_source", "event_type":"event_type", "url":"https://www.sample.com:80/endpoint\", "action":"UPDATE", "data": [ {"id":1,"description":"This is a payload."} ] }"
Step 3: Compute the hexdigest of the resulting string using MY_ALIGNI_SIGNATURE (the one from the webhook configuration in Aligni).
event_signature_computed = "v1=" + OpenSSL::HMAC.hexdigest( 'SHA256', MY_ALIGNI_SIGNATURE, basestring ) >>> "v1=5fbf84f8df06b8dcaac31d43c00cf5fa48877299a79d661a631f7a5bf779f045"
Step 4: Compare your_signature to X-ALIGNI-SIGNATURE. We recommend using an HMAC compare function for checking equality.
event_signature = request.headers["X-ALIGNI-SIGNATURE"] >>> "v1=5fbf84f8df06b8dcaac31d43c00cf5fa48877299a79d661a631f7a5bf779f045" if hmac_compare(event_signature_computed, event_signature) handle_request
Webhook Events
Webhook events allow servers to be notified when specific events are triggered. The contents of each webhook message follows the template below where the data
section contains information specific to each event.
webhook.ping
This event is triggered when a Webhook is created or updated. It is used by Aligni as a health check on the remote server to determine whether or not to enable the webhook. The data payload for this event is empty. Note that your client must respond to this event and this event may not be disabled in the webhook configuration.
- If the request is accepted by the remote server and responds with
200
, then the webhook is enabled and put into service. - If the request is not accepted by the remote server or responds with anything other than
200
, then the webhook is disabled and will not post requests.
Data Payload Example
This request contains and empty data payload.
[ { } ]
inventory_unit.change_data_capture
This event sends the inventory unit record, change data fields, and an InventoryRecord
when an inventory_unit
is created or updated.
Data Payload Example
The event below illustrates an inventory_unit
transfer to another location:
[ { "id" : "114", "part_id" : "752", "inventory_location_name" : "Headquarters", "inventory_location_shortname" : "CDHQ", "inventory_sublocation_name" : "Bin 9", "inventory_sublocation_id" : 7, "quantity" : "1.0", "cost_per_unit" : "24.5", "change_data" : { "inventory_sublocation_id" : 7, "updated_at" : "2020-07-02 15:21:43 -0700" }, "inventory_record" : { "id" : 14, "site_id" : 5, "part_id" : 752, "account_id" : 9, "record_type" : "Transfer", "comment" : "", "sublocation_src_id" : 1, "sublocation_dst_id" : 7, "created_on" : "2020-07-02 15:21:43 -0700", "inventory_transfer_id" : null, "purchase_order_id" : null, "inventory_consumption_record_id" : null, "unit_id" : 5, "asset_id" : null, "sublocation_src_bin" : null, "sublocation_dst_bin" : null, "lot_code" : null, "serial_number" : null, "datecode" : null, "purchase_item_id" : null, "quantity" : "1.0", "quantity_before" : null, "equipment_id" : null, "unit_cost" : "24.5", "extended_cost" : "24.5", "use_as_quantity" : "1200.0", "use_as_quantity_before" : null } } ]