This example shows you how to integrate Slack incoming webhooks to receive notifications in your workspace when a Smart Action e.g "Reject application" is triggered.
Demo
Create your Forest slack app
Follow Slack’s guide to create a new app in your workspace and start sending messages using Incoming Webhooks.
At the end of this guide, make sure your Slack app has the following features activated:
- Incoming Webhooks
- Interactive Components
- Bots
- Permissions
Once your Slack app has its shiny Incoming Webhook URL, you will be able to send your message in JSON as the body of an application/json POST request.
https://hooks.slack.com/services/YOUR_WORKSPACE_ID/YOUR_CHANNEL_ID/YOUR_SECRET_TOKEN
Connect your app from a Slack channel of your choice
Set up the webhook from your admin backend
From your project’s directory, simply run
$ npm install --save @slack/webhook
Add the Incoming Webhook URL to your .env
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/YOUR_WORKSPACE_ID/YOUR_CHANNEL_ID/YOUR_SECRET_TOKEN
This Incoming Webhook URL contains a secret key, please make sure it does not appear in your code.
Create the Smart Action and initialize the Incoming Webhook
Smart Action declaration
const { collection } = require('forest-express-sequelize');
// This file allows you to add to your Forest UI:
// - Smart actions: https://docs.forestadmin.com/documentation/reference-guide/actions/create-and-manage-smart-actions
// - Smart fields: https://docs.forestadmin.com/documentation/reference-guide/fields/create-and-manage-smart-fields
// - Smart relationships: https://docs.forestadmin.com/documentation/reference-guide/relationships/create-a-smart-relationship
// - Smart segments: https://docs.forestadmin.com/documentation/reference-guide/segments/smart-segments
collection('companies', {
actions: [
{
name: 'Reject application',
type: 'single',
fields: [
{
field: 'Reason(s) for rejection',
description: 'Please provide a reason for this decision',
type: ['Enum'],
enums: [
'Certificate of Incorporation',
'Proof of Address ID',
'Bank Statement ID',
],
required: true,
},
{
field: 'Comment',
description:
'This comment will only be displayed in your slack workspace message',
type: 'String',
widget: 'text area',
},
],
},
],
// ...
});
Smart Action logic
const express = require('express');
const {
PermissionMiddlewareCreator,
RecordsGetter,
} = require('forest-express-sequelize');
const { IncomingWebhook } = require('@slack/webhook');
const { companies } = require('../models');
const router = express.Router();
const permissionMiddlewareCreator = new PermissionMiddlewareCreator(
'companies'
);
const url = process.env.SLACK_WEBHOOK_URL;
// This file contains the logic of every route in Forest for the collection companies:
// - Native routes are already generated but can be extended/overridden - Learn how to extend a route here: https://docs.forestadmin.com/documentation/v/v6/reference-guide/routes/extend-a-route
// - Smart action routes will need to be added as you create new Smart Actions - Learn how to create a Smart Action here: https://docs.forestadmin.com/documentation/v/v6/reference-guide/actions/create-and-manage-smart-actions
// ...
// Initialize webhook
const webhook = new IncomingWebhook(url);
router.post(
'/actions/reject-application',
permissionMiddlewareCreator.smartAction(),
async (request, response) => {
// Get input form attributes
const attributes = await request.body.data.attributes.values;
const rejectionReason = attributes['Reason(s) for rejection'];
const comment = attributes['Comment'];
// Get selected company
const [selectedCompanyId] = await new RecordsGetter(
companies,
request.user,
request.query
).getIdsFromRequest(request);
const selectedCompany = await companies.findByPk(selectedCompanyId);
// Change company status to rejected
await companies.update(
{ status: 'rejected' },
{ where: { id: selectedCompanyId } }
);
response.send({ success: "Company's request to go live rejected!" });
// Trigger Slack webhook
await webhook.send({
text: 'An action has been triggered from Forest',
channel: 'C01CFGCADGF', // replace by your Slack channel ID
blocks: [
{
type: 'header',
text: {
type: 'plain_text',
text: 'Action triggered - Company application rejected :x:',
emoji: true,
},
},
{
type: 'divider',
},
{
type: 'section',
text: {
type: 'mrkdwn',
text: `${request.user.firstName} ${request.user.lastName} just rejected <https://app.forestadmin.com/Live-demo/Production/Operations/data/companies/index/record/companies/${selectedCompanyId}/summary|${selectedCompany.name}>'s request to go live!\n\n • *Reason for rejection:* ${rejectionReason[0]}\n • *Comment:* ${comment}`,
},
accessory: {
type: 'image',
image_url: `${selectedCompany.logoUrl}`,
alt_text: 'company logo',
},
},
{
type: 'section',
text: {
type: 'mrkdwn',
text: 'For more details on the company',
},
accessory: {
type: 'button',
text: {
type: 'plain_text',
text: 'View Notes',
emoji: true,
},
value: 'null',
url: `https://app.forestadmin.com/Live-demo/Production/Operations/data/companies/index/record/companies/${selectedCompanyId}/collaboration`,
action_id: 'button-action',
},
},
],
});
}
);
module.exports = router;
To learn more about composing messages using the Slack API, please visit the
To learn more about error handling of Slack Interactive Webhooks, please visit the Slack changelog.