Send a Notification to Discord Workflow
In this tutorial, we create an active workflow that will send a notification to Discord.
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 on the + New workflow
button.

Next, rename the workflow by typing in the Name
input field. You can also provide an optional description of the workflow's functionality by typing in the Description
input field.
Nodes and Connections
Too add nodes to the workflow, click on + Add Node
button and then the + Add
button of a specific node.
For this workflow, the overall node layout will be:
- The
Active Start
node outputs$active_start.request
and$active_start.response
objects which represent proxied requests the workflow was initiated on and their corresponding responses. - The request and response objects will be passed to the
Javascript
node. - Once the request and response objects have been processed by the script in the
Javascript
node, the workflow will end.
Creating and Sending a Request
Click on the Javascript
node to access its editor. Then, click within the coding environment, select all of the existing code, and replace it with the following script:
NOTE
Replace <webhook-url>
with the URL of your own Discord webhook.
// 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 message = {
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("<webhook-url>", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(message)
});
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}`;
}
}
Next, ensure the $active_start.request
and $active_start.response
objects are referenced as input to the Javascript
node.

Once these steps are completed, close the editor window and click on the Save
button to update and save the configuration.
Script Breakdown
To be able to send a fetch request, the Request
class and the fetch()
function are imported from the caido:http
module.
// Request object under the alias of FetchRequest.
import { Request as FetchRequest, fetch } from "caido:http";
Next, an asynchronous function is defined that takes the input
and the sdk
interface object as parameters.
export async function run(input, sdk) {
The body data of the fetch request is defined as an object and stored in the message
variable.
// Discord webhook data.
const message = {
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()
}]
};
Then, using new FetchRequest()
the fetch request is defined, using a Discord Webhook URL as the input
parameter of the constructor. The HTTP method
, headers
, and body
data are specified in the RequestOpts object parameter.
// Create a new request to Discord webhook.
const fetchRequest = new FetchRequest("<webhook-url>", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(message)
});
Then, fetch(fetchRequest)
is used to send the constructed request.
Since we must wait for the request to be sent and response to be returned, the await
directive is used.
The response is stored in the response
variable.
try {
const response = await fetch(fetchRequest);
By accessing the response
object properties, we can print the data to the backend logs.
// 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}`;
}
}
Testing the Workflow
To test the workflow, right-click on a request to open the context menu, hover over the Run workflow
option, and select the workflow.

The Result
You will receive a message in your Discord channel.

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