Skip to main content

What is a webhook?

A webhook is a mechanism that enables an application to receive automatic notifications or data updates by sending a request to a specified URL when a particular event or trigger occurs.

There are various types of webhooks that serve different purposes. One such type is the responder, which is a special webhook that responds to requests with a certain predefined response. A responder is a handy tool when you need to simulate an HTTP endpoint that's not yet implemented or even create a quick "honeypot" endpoint. Responders can also serve as a quick and easy way to test HTML, JavaScript, and CSS code.

On this page, you can find several guides on how to create different types of responders.

NOTE

Each user on secutils.dev is assigned a randomly generated dedicated subdomain. This subdomain can host user-specific responders at any path, including the root path. For instance, if your dedicated subdomain is abcdefg, creating a responder at /my-responder would make it accessible via https://abcdefg.webhooks.secutils.dev/my-responder.

Return a static HTML page

In this guide you'll create a simple responder that returns a static HTML page:

  1. Navigate to Webhooks → Responders and click Create responder button
  2. Configure a new responder with the following values:
Name
HTML Responder
Path
/html-responder
Method
GET
Headers
Content-Type: text/html; charset=utf-8
Body
<!DOCTYPE html>
<html lang="en">
<head>
<title>My HTML responder</title>
</head>
<body>Hello World</body>
</html>
  1. Click the Save button to save the responder
  2. Once the responder is set up, it will appear in the responders grid along with its unique URL
  3. Click the responder's URL and observe that it renders text Hello World

Watch the video demo below to see all the steps mentioned earlier in action:

Emulate a JSON API endpoint

In this guide you'll create a simple responder that returns a JSON value:

  1. Navigate to Webhooks → Responders and click Create responder button
  2. Configure a new responder with the following values:
Name
JSON Responder
Path
/json-responder
Method
GET
Headers
Content-Type: application/json
Body
{
"message": "Hello World"
}
  1. Click the Save button to save the responder
  2. Once the responder is set up, it will appear in the responders grid along with its unique URL
  3. Click the responder's URL and use an HTTP client, like cURL, to verify that it returns a JSON value

Watch the video demo below to see all the steps mentioned earlier in action:

Use the honeypot endpoint to inspect incoming requests

In this guide, you'll create a responder that returns an HTML page with custom Iframely meta-tags, providing a rich preview in Notion. Additionally, the responder will track the five most recent incoming requests, allowing you to see exactly how Notion communicates with the responder's endpoint:

  1. Navigate to Webhooks → Responders and click Create responder button
  2. Configure a new responder with the following values:
Name
Notion Honeypot
Path
/notion-honeypot
Tracking
5
Headers
Content-Type: text/html; charset=utf-8
Body
<!DOCTYPE html>
<html lang="en">
<head>
<meta property="iframely:image"
content="https://raw.githubusercontent.com/secutils-dev/secutils/main/assets/logo/secutils-logo-initials.png" />
<meta property="iframely:description"
content="Inspect incoming HTTP request headers and body with the honeypot endpoint" />
<title>My HTML responder</title>
</head>
<body>Hello World</body>
</html>
  1. Click the Save button to save the responder
  2. Once the responder is set up, it will appear in the responders grid along with its unique URL
  3. Copy responder's URL and try to create a bookmark for it in Notion
  4. Note that the bookmark includes both the description and image retrieved from the rich meta-tags returned by the responder
  5. Go back to the responder's grid and expand the responder's row to view the incoming requests it has already tracked

Watch the video demo below to see all the steps mentioned earlier in action:

Generate a dynamic response

In this guide, you'll build a responder that uses a custom JavaScript script to generate a dynamic response based on the request's query string parameter:

NOTE

The script should be provided in the form of an Immediately Invoked Function Expression (IIFE). It runs within a restricted version of the Deno JavaScript runtime for each incoming request, producing an object capable of modifying the default response's status code, headers, or body. Request details are accessible through the global context variable. Refer to the Annex: Responder script examples for a list of script examples, expected return value and properties available in the global context variable.

  1. Navigate to Webhooks → Responders and click Create responder button
  2. Configure a new responder with the following values:
Name
Dynamic
Path
/dynamic
Tracking
5
Headers
Content-Type: text/html; charset=utf-8
Script
(async () => {
return {
// Encode body as binary data.
body: Deno.core.encode(
context.query.arg ?? 'Query string does not include `arg` parameter'
)
};
})();
  1. Click the Save button to save the responder
  2. Once the responder is set up, it will appear in the responders grid along with its unique URL
  3. Click the responder's URL and observe that it renders text Query string does not include arg parameter
  4. Change the URL to include a query string parameter arg and observe that it renders the value of the parameter
  5. Go back to the responder's grid and expand the responder's row to view the incoming requests it has already tracked
  6. Notice that all requests are tracked, including queries with and without the arg parameter

Watch the video demo below to see all the steps mentioned earlier in action:

Annex: Responder script examples

In this section, you'll discover examples of responder scripts capable of constructing dynamic responses based on incoming request properties. Essentially, each script defines a JavaScript function running within a restricted version of the Deno JavaScript runtime. This function has access to incoming request properties through the global context variable. The returned value can override default responder's status code, headers, and body.

The context argument has the following interface:

interface Context {
// An internet socket address of the client that made the request, if available.
clientAddress?: string;
// HTTP method of the received request.
method: string;
// HTTP headers of the received request.
headers: Record<string, string>;
// HTTP path of the received request.
path: string;
// Parsed query string of the received request.
query: Record<string, string>;
// HTTP body of the received request in binary form.
body: number[];
}

The returned value has the following interface:

interface ScriptResult {
// HTTP status code to respond with. If not specified, the default status code of responder is used.
statusCode?: number;
// Optional HTTP headers of the response. If not specified, the default headers of responder are used.
headers?: Record<string, string>;
// Optional HTTP body of the response. If not specified, the default body of responder is used.
body?: Uint8Array;
}

Override response properties

The script overrides responder's response with a custom status code, headers, and body:

(async () => {
return {
statusCode: 201,
headers: {
"Content-Type": "application/json"
},
// Encode body as binary data.
body: Deno.core.encode(
JSON.stringify({ a: 1, b: 2 })
)
};
})();

Inspect request properties

This script inspects the incoming request properties and returns them as a JSON value:

(async () => {
// Decode request body as JSON.
const parsedJsonBody = context.body.length > 0
? JSON.parse(Deno.core.decode(new Uint8Array(context.body)))
: {};

// Override response with a custom HTML body.
return {
body: Deno.core.encode(`
<h2>Request headers</h2>
<table>
<tr><th>Header</th><th>Value</th></tr>
${Object.entries(context.headers).map(([key, value]) => `
<tr>
<td>${key}</td>
<td>${value}</td>
</tr>`
).join('')}
</table>

<h2>Request query</h2>
<table>
<tr><th>Key</th><th>Value</th></tr>
${Object.entries(context.query ?? {}).map(([key, value]) => `
<tr>
<td>${key}</td>
<td>${value}</td>
</tr>`
).join('')}
</table>

<h2>Request body</h2>
<pre>${JSON.stringify(parsedJsonBody, null, 2)}</pre>
`)
};
})();

Generate images and other binary content

Responders can return not only JSON, HTML, or plain text, but also binary data, such as images. This script demonstrates how you can generate a simple PNG image on the fly. PNG generation requires quite a bit of code, so for brevity, this guide assumes that you have already downloaded and edit the png-generator.js script from the Secutils.dev Sandbox repository (you can find the full source code here). The part you might want to edit is located at the bottom of the script:

(() => {
// …[Skipping definition of the `PngImage` class for brevity]…

// Generate a custom 100x100 PNG image with a white background and red rectangle in the center.
const png = new PngImage(100, 100, 10, {
r: 255,
g: 255,
b: 255,
a: 1
});

const color = png.createRGBColor({
r: 255,
g: 0,
b: 0,
a: 1
});

png.drawRect(25, 25, 75, 75, color);

return {
body: png.getBuffer()
};
})();