Step-by-Step Guide: Sending Tape Automation Errors to Slack

Following on from Dirks post [✅ Solution] Sending data from Tape to an external app via webhooks? - #8 by dirk_s and this thread Discord integration as team communication.

The use case Dirk mentions seemed a good and interesting one so I thought I would take the concept (hopefully he doesn’t mind) and build it out. It covers a number of different areas that can be taken and used for other things. As an example, I will cover:

  1. Configuring Tape workflows to report errors to a verified URL
  2. In Tape convert the webhook payload into an HTML table
  3. Display the HTML table reliably on a Tape record
  4. Use a record calculation field to build a record name
  5. Use an http.post script to send relevant formatted data from Tape to an external Webhook

What are we going to build

We want Tape to report automation errors and have notifications of those errors delivered to an external messaging service.

Now for various reasons, I don’t want to use Teams for this, so I will use Slack. I don’t use it and I don’t have any clients that do either so it’s nice to use something different.

Tape has a built-in system to send automation errors to a webhook however we can’t use that to send the information directly to Slack for one thing the payload will not be formatted the way Slack wants it. So we need something to sit in between and we will just use a Tape app.

Initial App setup

We don’t really know yet what fields we want so we will just go with the default. What we do need is an Automation with a webhook trigger however again we don’t know yet what we are going to do with it fully so just create it and leave it as default.

:information_source: What this gives us is the webhook URL which we need in the next stage.

The error report

We now have what we need to set up the error reporting to do this we go into Automations and then Run history in the top right there is the ... button. Then click on the Webhook notification option:


When this comes up you can paste in the Webhook URL generated in the first step:

At this point, it will say Inactive as it needs to be verified.

Verification

Go back to the automation you created to receive the notifications and if the payload window is still empty hit the refresh button. you should then have something that looks like:


Which gives us the information we need to send back to Tape to verify.

I find it easiest to do this in Visual Studio Code but use whatever tools you are happy with to edit and then run a cURL:

curl -X POST https://api.tapeapp.com/v1/hook/{{Replace with the hook_id}}/verify/validate \

-u {{replace with your Tape API key}} \

-H "Content-Type: application/json" \

--data '{

"code": "{{replace with the payload/code}}"

}'

you should then get a response that looks something like:

{
  "hook_id": 557,
  "status": "active",
  "type": "run.failed",
  "url": "https://tapeapp.com/api/catch/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ3b3JrZmxvd0RlZklk.ZjHEjF-8u0Uiz0tJEXxwRw7suDw2s3u8kGjwZRB0k0s"
}

:information_source: Tape developer docs

Now your Webhook Notification should be marked as active and it is ready to send an error to your error log app.

What information do we get

It is time to see what information we get from an error so we can make some decisions as to what we send to Slack etc.

You need to make an automation error so we get a notification sent once you have done this go back to your Webhook trigger automation the payload should now look like this:


We now have the information we need to build out the App.

The Notification App build

If you change the payload view option from Variables to Payload you get the following:

{
"hook_id": 479,
"type": "run.failed",
"app_id": 37746,
"organization_id": 1484,
"organization_name": "Melotte Consulting",
"organization_slug": "jmc",
"workflow_def_id": 128437,
"workflow_def_name": "Send new record details to Slack",
"workflow_def_broken": false,
"workflow_def_url": "https://tapeapp.com/jmc/(focus//root-modal:workflow-center/edit-workflow/128437)",
"workflow_run_id": 6669588,
"error_message": "Invalid URL",
"triggered_on_record_id": 60067223,
"triggered_on_record_url": "https://tapeapp.com/jmc/(focus//main-modal:record/60067223)"
}

Wouldn’t it be nice if rather than creating a field for each item on your record you could just display all the information in one formatted table like:


and that is what we are going to do.

Building the table

To do this we need two fields on our record:

Until the Tape team gives us some sort of variable field or multiline plain text field (which i am sure they will as they are amazing) we have some hoops to jump through but it is still not too bad.

The Calculation field

what you need in the calculation field is:

const d = Buffer.from(@payload, 'base64').toString('utf-8');

`${d}`

:information_source: If you have named the fields differently obviously replace @payload with whatever you have called your field.

:information_source: I will explain the whole Buffer.from thing later but this is one of the hoops we have to jump through.

The Automation

If we move back to the automation we can build the table:
We use a script block (mine doesn’t normally look this neat, I have added lots of comments to try and explain what is going on):

let payload = webhook_payload_parsed;

/**
* Generates an HTML table from a JSON object.
* @param {Object} data - The JSON object.
* @returns {string} - The HTML table.
*/

function generateHTMLFromJSON(data) {
let html = '<table style="border-collapse: collapse; width: 100%;">';
// Loop through each key in the data object
for (let key in data) {
html += '<tr>';

// Add the table header
html += `<th style="border: 1px solid #1B98A6; text-align: left; padding: 8px; background-color: #f2f2f2; color: #1E4359">${key}</th>`;

// Check if the value is an object
if (typeof data[key] === 'object') {

// Recursively call the function to generate HTML for nested objects
html += '<td style="border: 1px solid #1B98A6; text-align: left; padding: 8px;">' + generateHTMLFromJSON(data[key]) + '</td>';

} else {

// Add the table data
html += `<td style="border: 1px solid #1B98A6; text-align: left; padding: 8px;">${data[key]}</td>`;
}
html += '</tr>';
}
html += '</table>';
return html;
}

const htmlString = generateHTMLFromJSON(payload);

// console.warn('____');
// console.log('html', htmlString);
var_html = htmlString

We have our table now we need a record to display it so we use a Create a record block and add our variable html to it:

  1. Create a record
  2. Set to Calculation
  3. We base64 encode our HTML to protect it

This is the other half of our Buffer.from when you put data into a Multiple line text field it messes with it adding additional tags and we don’t want that. You can use stripTags, Loadash or plain Regex to try and tidy it but the easiest and most reliable way I have found (although possibly not the most resource-friendly) of dealing with this is to encode the data and then decode it which is what we have done and we now have a nice formatted table in our record.

:information_source: One of the advantages of this method for things like notifications is that if the payload changes say for a different type of notification it doesn’t matter it will just generate a table with the new data regardless of the Key names.

The rest of the app

What other fields you need are going to depend on what information you want to filter your errors on for example if you are building one error log app for multiple Organisations you are probably going to want some way of filtering on the Organisation so pulling out one or all of the organization_id, organization_name, organization_slug is going to be required likewise if it is for a single org you don’t really need any of that.

So your automation could look like:


Which would provide you with a record looking like this:

The Name

Before we move on, the record Name field is a calculation field that pulls data from different areas:

const e = `ERR${String(@Unique ID).padStart(3, '0')}`;

const d = date_fns.format(date_fns_tz.utcToZonedTime(@Created on,'Europe/london'), 'yyMMdd-HHmm');

const s = @slug;

`${e} - ${d} ${s}`

Slack

Next, we need to build our connection to Slack. Head over to Your apps and click the create new app button (top right), you will get a popup:


Select From scratch and fill in the form that comes up:

click Create App

Configure the Slack app

Near the top of the page that comes up is:


Select the Incoming Webhooks option which will open a new window, you need to turn on the Activate Incoming Webhooks

At which point further options will appear and you need to click on the Add New Webhook to Workspace button when you click the button you will be asked for a channel you want to use, select the channel you want to post to and click Allow

You will be taken back to your Incoming Webhooks page and the Webhook URL will be available to copy.

Extending the Tape automation

Webhook URL

You need to put your Slack webhook URL somewhere for this example we are just going to drop it in a calculation block:

NOTE: The whole URL must be enclosed if it is not it will not work.

Sending to Slack

Next, we need a script block, which is going to build our message and send it to Slack:

// Make some variables from our Payload
// Build a Title
const title = `Error on record ${jsonata('triggered_on_record_id').evaluate(webhook_payload_parsed)} with automation ${jsonata('workflow_def_name').evaluate(webhook_payload_parsed)}`;

// Get the Organisation name
const orgName = jsonata('organization_name').evaluate(webhook_payload_parsed);

// The Workflow Name
const workflowName = jsonata('workflow_def_name').evaluate(webhook_payload_parsed);

//The Workflow URL
const workflowURL = jsonata('workflow_def_url').evaluate(webhook_payload_parsed);

//The ofending Record ID
const recordID = jsonata('triggered_on_record_id').evaluate(webhook_payload_parsed);

//The offending Record URL
const recordURL = jsonata('triggered_on_record_url').evaluate(webhook_payload_parsed);

// Build a URL for this record
const errorURL = `https://tapeapp.com/jmc/(focus//main-modal:record/${created_automation_error_ID})`;

  
//Send the message to Slack
const response = await http.post(
var_slackadd,
{
headers: {
"Content-Type": "application/json",
},
data: {
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": `*${title}*\nHi an automation in Tape has errored\nThis error needs to be checked and fixed:`
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": `The error has occurred in the organisation: *${orgName}*`
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": `The workflow is <${workflowURL}|*${workflowName}*>`
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": `The record that generated the error is <${recordURL}|*${recordID}*>`
}
}
{
"type": "divider"
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": `The Error log record is: <${errorURL}|*${created_automation_error_ID}*>`
}
]
}
});

console.info(JSON.stringify(response));

:information_source: Even though the section types are mrkdwn they do not accept normal markdown.

The result

Now if we run the tape automation it sends a message to Slack which looks like this:


A formatted message with all the relevant information including links back to the relevant Tape pages

Recap

So what have we done above:

  1. Configured Tape workflows to report errors to a verified URL
  2. In Tape take a webhook payload and convert it into an HTML table
  3. Displayed the HTML reliably on a Tape record
  4. Used a record calculation field to build a record name
  5. Configured Slack to receive messages from Tape
  6. Used an http.post script to send relevant formatted data from Tape to a Webhook

Update

There are a couple of things that should be added:

  1. If you are using this Error Log within the same reporting Organisation it is very easy to end up in a loop.
  2. These lines:
"triggered_on_record_id": 60067223,
"triggered_on_record_url": "https://tapeapp.com/jmc/(focus//main-modal:record/60067223)"

do not always exist in the Payload as a workflow is not always related to a record.

Fixes

You can add a filter at the start of your Webhook automation to check that the workflow generating the error is not itself:


you can find the workflow ID of your workflow with:

console.info(current_workflow_id)

If you change any fields you are trying to add record information to use a calculation instead of adding the @ variable:

jsonata('triggered_on_record_ur').evaluate(webhook_payload_parsed)

it doesn’t error when it can’t find that key unlike when you use the @ and Add Value

9 Likes

Hi Jason,

really really great to see how you use the webhook notifications we built back then!:100::raised_hands:t3:
I remember exactly how it was designed due to user requests not to do it via email but via webhooks and I’m really happy to see how you activate it and then it’s such a clearly designed push message in Slack. And rest assured Jason that we are going to make the plain multitext field for payloads very high on the list when we upgrade the record! I can see the enormous advantages it will bring, especially with your examples!

Thanks again for your amazing contribution and your great support
Leo

2 Likes