What are webhooks?
A webhook is a means of proactively informing one system when there is a change or event in another one. At Mainstay, webhooks allow a partner to receive notifications of escalations to an external API (in addition to email).
Configuration
To configure a new webhook, visit https://app.mainstay.com/settings/api-tokens (Settings > Mainstay API). This table shows a list of all configured webhooks.
- Name - a custom name given to this configuration
- Enable - a boolean toggle that sets whether to send data to this webhook URL
- URL - the web address of the external system that is expecting data
- Event Type - the kind of event that would be sent to this URL; at present, this is only Escalations
- Revoke Access - remove this configuration
Creating a Configuration
- Click Create New to configure a webhook.
- Enter a custom Name for this configuration and the exact URL that will receive the data.
- Click Create Shared Secret
- Copy this Shared Secret to a safe place. This is used for verifying that data received at this URL was sent from Mainstay.
- To enable or disable this configuration, click the toggle in the Enabled column.
Removing a Configuration
- Click Revoke Access to pull up a confirmation screen.
- Click Yes, Revoke to permanently delete this configuration.
- Note that it is not possible to edit a configuration. If you need to change the Name or URL, or if you need to reset the Shared Secret, simply delete the existing configuration and create a new one.
Escalations Rules
To configure an escalation rule, visit https://app.mainstay.com/settings/escalation (Settings > Email Forwarding & Escalations). See the support article on Email Forwarding & Escalations to learn more about manual and automatic escalations.
If the webhooks feature is available on your account - regardless of whether you have any enabled configurations - you will see an informational banner above both the Manual Forwarding Suggestions and Automatic Escalation Rules tables:
You will also be able to configure rules without an email recipient - the field becomes optional:
All escalation events will send to all enabled webhook configurations. Even if there is an email recipient, an escalation event will still be sent to your webhook configurations.
You can also have campaign Script escalations and responses to Script questions escalate to webhooks if you so choose:
Escalations Event Payload
Here is an example of an automatic escalation sent to a webhook URL as a POST request:
{
"topic_schema_version": "1.0",
"topic": "escalation.triggered",
"type": "notification_event",
"event_id": "afe4be18-bfbe-4967-b9cd-f42d534f4862",
"event_time": "2022-05-27T17:55:22.054895",
"attempts": 1,
"data": {
"event_id": "afe4be18-bfbe-4967-b9cd-f42d534f4862",
"escalation_id": "xzzWPnuiewF4cJW5C",
"contact_id": "5e41985fe64395007d593825",
"escalated_message": "test from James M die",
"institution_id": "productTest",
"occurred_at": "2022-05-27T17:55:13+00:00",
"non_english_translation": null,
"escalation_type": "sensitiveMatch",
"conversation_link": "https://app.mainstay.com/conversations/5e41985fe64395007d593825",
"contact_information": {
"mainstay_id": "5e41985fe64395007d593825",
"first_name": "James",
"last_name": "Mayr",
"preferred_name": "Jimbo",
"email": "james@mainstay.com",
"phone": "5553141592",
"enrollment_id": "abc",
"crm_id": "xyz",
"custom_fields": {
"favoritecolor": "blue"
},
"latest_chat_history": [],
"logs": [
{
"incoming": true,
"message_body": "test from James M die"
},
{
"incoming": false,
"message_body": "You said die and I'm programed to treat that language seriously. If there's an emergency, call 911"
}
]
}
}
}
Note that event_id and event_time refer to Mainstay's attempt to send this payload to the webhook URL, while data.escalation_id and data.occurred_at refer to the escalation event itself - in this case, the bot recognizing that the incoming message contained the sensitive word "die".
In addition to this payload, the following headers are included:
{
"Content-Type": "application/json",
"Mainstay-Signature": str,
"Mainstay-Timestamp": str(seconds-since-epoch),
}
This mainstay-signature value is derived from the Shared Secret and the payload as a JSON Web Token. This can be used to ensure that the data is truly coming from Mainstay (without revealing the Shared Secret). See below for a technical guide.
Creating A Webhook Endpoint
A webhook endpoint is an HTTP or HTTPS endpoint on your server with a URL. Note that once your new webhook endpoint is publicly accessible, it must be HTTPS.
Set up an HTTP endpoint on your local machine which will do the jobs of verifying the incoming POST request signatures, consuming the request payload, and returning a response to Mainstay.
The incoming request payload has the headers and schema as detailed above.
The webhook URL should send return a response with one of the following shapes back to Mainstay.
Successful requests:
{"statusCode": 200, "body": {"message": str}}
Failed requests:
400 - Bad request
{"statusCode": 400, "body": {"error": str}}
500 - Something else went wrong
{"statusCode": 500, "body": {"error": str}}
Securing the Webhook
You can use a combination of the headers and your shared secret to verify the “Mainstay-Signature” header, ensuring that the request hasn’t been intercepted or altered on its way from Mainstay to your endpoint.
The “Mainstay-Signature” Header
Webhook requests from Mainstay are signed with the HS256 algorithm, which implements the SHA-256 hashing method to encode a combination of the request body and the “Mainstay-Timestamp” header, using your shared secret as the key. This comprises the “Mainstay-Signature” header. Below you’ll see an example of Python code used to generate these signatures using the PyJWT library.
signature = jwt.encode(
payload={**request_body, **{"iat": seconds_since_epoch}},
key=shared_secret,
)
Verifying the “Mainstay-Signature”
In order to verify the “Mainstay-Signature”, you can generate the signature within your endpoint following the same steps. If those signatures match, you can be sure the webhook request is secure. Below, you’ll see an example of Python code used to do this verification:
import jwt
import os
from typing import Dict, Any
from django.http.request import HttpRequest
SHARED_SECRET = os.getenv("MAINSTAY_SHARED_SECRET")
def signatures_match(payload: Dict[str, Any], timestamp: str, mainstay_signature: str) -> bool:
signature = jwt.encode(payload={**payload, **{"iat": timestamp}}, key=SHARED_SECRET)
return signature == mainstay_signature
@app.route('/mainstay_webhooks', methods=['POST'])
def webhook(request: HttpRequest):
payload = request.body
headers = request.headers
timestamp = headers["Mainstay-Timestamp"]
mainstay_signature = headers["Mainstay-Signature"]
if signatures_match(payload=payload, timestamp=timestamp, mainstay_signature=mainstay_signature):
...
# consume payload
return Response(status=200, {"message": "Success!"})
else:
return Response(status=400, {"error": "Unable to verify signature"})
There are many JSON Web Token libraries used for token signing and verification in other languages.
Comments
0 comments
Article is closed for comments.