Sending a notification to Discord
In this tutorial, we will learn how to use an Active Workflow to send a notification to Discord. This method can also be used with other types of workflows.
We will use Caido's HTTP Module which provides an implementation of the Fetch API. With this module, you can create and send asynchronous HTTP requests and handle their responses.
NOTE
The request and response objects of this module differ from those used in the Backend SDK and Workflow SDK. Due to this, their properties and methods differ as well. Additionally, they are not routed through the proxy and must adhere to the HTTP specification in order to be interpreted correctly.
Creating an Active Workflow
To begin, navigate to the Workflows interface, select the Active
tab, and click the + New workflow
button.

Next, click, hold and drag a Javascript
Node into the Workflow Editor field and make Connections to the Active Start
and Active End
Nodes. Then click on the Javascript
Node to access its detailed view.

Creating and Sending a Request
Now, click within the coding environment, select all the existing code, and delete it.
To send a request, you will first need to import the Request
class and the fetch()
function from the caido:http
module.
// Request object under the alias of FetchRequest.
import { Request as FetchRequest, fetch } from "caido:http";
Next, define an asynchronous function and the parameters of your Discord message.
export async function run(input, sdk) {
// Discord webhook data.
const webhookData = {
username: "Caido Bot",
avatar_url: "https://caido.io/images/logo.color.webp",
content: "Message from Caido Workflow",
embeds: [{
title: "Webhook Fetch Request",
description: "Hello World!",
color: 14329120,
fields: [
{
name: "Field A",
value: "Value A",
inline: true
},
{
name: "Field B",
value: "Value B",
inline: true
}
// You could also add elements from the request like
// {
// name: "Host",
// value: input.request.getHost(),
// inline: true
// },
],
footer: {
text: "Sent via Caido"
},
timestamp: new Date().toISOString()
}]
};
Then, define the request object, using your Discord Webhook URL as the input parameter of the constructor and specify the HTTP method and Content-Type header in the RequestOpts parameter object.
// Create a new request to Discord webhook.
const fetchRequest = new FetchRequest("YOUR-DISCORD-WEBHOOK-URL", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(webhookData),
});
We must await for the request to be sent and processed before we are able to obtain data from the response. By accessing the response properties, we can print the data to the backend logs.
try {
const response = await fetch(fetchRequest);
// Create response data object.
const responseData = {
status: response.status,
statusText: response.statusText,
headers: Object.fromEntries(response.headers.entries())
};
// Log the response data with proper formatting.
sdk.console.log("Response data:", JSON.stringify(responseData, null, 2));
// For Discord webhooks, 204 means success.
if (response.status === 204) {
return "Webhook sent successfully";
}
// If not 204, get the error details from response.
const errorBody = await response.text();
return `Webhook failed: ${errorBody}`;
} catch (error) {
return `Error: ${error.message}`;
}
}
Finally, click the Save
button in the bottom right corner of the Workflow Editor.
TIP
To view the entire script, expand the following:
Full Script
// Request object under the alias of FetchRequest.
import { Request as FetchRequest, fetch } from "caido:http";
export async function run(input, sdk) {
// Discord webhook data.
const webhookData = {
username: "Caido Bot",
avatar_url: "https://caido.io/images/logo.color.webp",
content: "Message from Caido Workflow",
embeds: [
{
title: "Webhook Fetch Request",
description: "Hello World!",
color: 14329120,
fields: [
{
name: "Field A",
value: "Value A",
inline: true,
},
{
name: "Field B",
value: "Value B",
inline: true,
},
],
footer: {
text: "Sent via Caido",
},
timestamp: new Date().toISOString(),
},
],
};
// Create a new request to Discord webhook.
const fetchRequest = new FetchRequest("YOUR-DISCORD-WEBHOOK-URL", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(webhookData),
});
try {
const response = await fetch(fetchRequest);
// Create response data object.
const responseData = {
status: response.status,
statusText: response.statusText,
headers: Object.fromEntries(response.headers.entries()),
};
// Log the response data with proper formatting.
sdk.console.log("Response data:", JSON.stringify(responseData, null, 2));
// For Discord webhooks, 204 means success.
if (response.status === 204) {
return "Webhook sent successfully";
}
// If not 204, get the error details from response.
const errorBody = await response.text();
return `Webhook failed: ${errorBody}`;
} catch (error) {
return `Error: ${error.message}`;
}
}
Using the Active Workflow
To use your newly created Workflow, right click on a request to open up the context menu. Hover over the Run workflow
option and select the given name.

Soon after, you will receive a message in your Discord channel.

INFO
Within the logs, the message will resemble:
2025-04-09T00:45:25.697833Z INFO main service|workflow: Executing workflow (g:58) as task
2025-04-09T00:45:25.697858Z INFO main service|task: Running task
2025-04-09T00:45:25.697862Z INFO main service|workflow: Workflow (g:58) task assigned ID: 26
2025-04-09T00:45:26.134839Z INFO executor:0|arbiter:7 js|sdk: Response data:, {
"status": 204,
"statusText": "No Content",
"headers": {
"date": "Wed, 09 Apr 2025 00:45:26 GMT",
"content-type": "text/html; charset=utf-8",
"connection": "keep-alive",
"set-cookie": "_cfuvid=.E8ALL.xBWASGB1xARc0HgFKDv10bpItHt35AsAKJDE-1744159526028-0.0.1.1-604800000; path=/; domain=.discord.com; HttpOnly; Secure; SameSite=None",
"strict-transport-security": "max-age=31536000; includeSubDomains; preload",
"x-ratelimit-bucket": "3d2712a9e4fe17cc9d3fed4a8e672e5f",
"x-ratelimit-limit": "5",
"x-ratelimit-remaining": "4",
"x-ratelimit-reset": "1744159527",
"x-ratelimit-reset-after": "1",
"via": "1.1 google",
"alt-svc": "h3=\":443\"; ma=86400",
"cf-cache-status": "DYNAMIC",
"report-to": "{\"endpoints\":[{\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=ZIGoTFpSBw9RLoXTZmN0CKYNnESTcIYHDgSl42ygSs1E9uOAgvjN%2FMmks8w9SLiHDAzyu5n8WDyMRHcPiyYa0LkUcpMyXEaoPd0c7HE9rHkCh24fR55k2qRmgTJL\"}],\"group\":\"cf-nel\",\"max_age\":604800}",
"nel": "{\"success_fraction\":0,\"report_to\":\"cf-nel\",\"max_age\":604800}",
"x-content-type-options": "nosniff",
"reporting-endpoints": "csp-sentry=\"https://o64374.ingest.sentry.io/api/5441894/security/?sentry_key=8fbbce30bf5244ec9429546beef21870&sentry_environment=stable\"",
"content-security-policy": "frame-ancestors 'none'; default-src https://o64374.ingest.sentry.io; report-to csp-sentry; report-uri https://o64374.ingest.sentry.io/api/5441894/security/?sentry_key=8fbbce30bf5244ec9429546beef21870&sentry_environment=stable",
"server": "cloudflare",
"cf-ray": "92d5fb4c58d5f7ab-LAX"
}
}
2025-04-09T00:45:26.135041Z INFO executor:0|arbiter:7 service|task: Task (26) done
2025-04-09T00:45:26.135079Z INFO main service|task: Finishing task 26