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.
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:
- Navigate to Webhooks → Responders and click Create responder button
- Configure a new responder with the following values:
Name |
|
Path |
|
Method |
|
Headers |
|
Body |
|
- Click the Save button to save the responder
- Once the responder is set up, it will appear in the responders grid along with its unique URL
- 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:
- Navigate to Webhooks → Responders and click Create responder button
- Configure a new responder with the following values:
Name |
|
Path |
|
Method |
|
Headers |
|
Body |
|
- Click the Save button to save the responder
- Once the responder is set up, it will appear in the responders grid along with its unique URL
- 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:
- Navigate to Webhooks → Responders and click Create responder button
- Configure a new responder with the following values:
Name |
|
Path |
|
Tracking |
|
Headers |
|
Body |
|
- Click the Save button to save the responder
- Once the responder is set up, it will appear in the responders grid along with its unique URL
- Copy responder's URL and try to create a bookmark for it in Notion
- Note that the bookmark includes both the description and image retrieved from the rich meta-tags returned by the responder
- 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:
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.
- Navigate to Webhooks → Responders and click Create responder button
- Configure a new responder with the following values:
Name |
|
Path |
|
Tracking |
|
Headers |
|
Script |
|
- Click the Save button to save the responder
- Once the responder is set up, it will appear in the responders grid along with its unique URL
- Click the responder's URL and observe that it renders text
Query string does not include
argparameter
- Change the URL to include a query string parameter
arg
and observe that it renders the value of the parameter - Go back to the responder's grid and expand the responder's row to view the incoming requests it has already tracked
- 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()
};
})();