Skip to content

HTTP

The HTTP service in Flows provides a way for apps and blocks to receive and respond to HTTP requests from external systems. This allows your Flows applications to expose their functionality to the outside world. Receiving webhooks and exposing APIs become straightforward with this service.

HTTP endpoints are often set up during the lifecycle onSync phase and can trigger events to integrate with workflows.

This service is essential for:

  • Building webhook receivers for services like GitHub, Stripe, or Slack;
  • Creating API endpoints for external applications;
  • Building integration bridges to external systems using complex HTTP-based authentication flows like OAuth or OIDC;

To add HTTP capabilities to an app or a block, include an http property in its definition.

import { http } from "@slflows/sdk/v1";
{
// ...
http: {
onRequest: async ({ request: { method, path }}) => {
// Respond to the request
await http.respond(input.request.requestId, {
statusCode: 200,
body: { message: `Received ${method} on ${path}` },
});
},
},
};

When you define HTTP capabilities for an app or block, Flows automatically creates a unique endpoint URL.

  • App endpoints: Flows generates a unique URL for each app installation
  • Block endpoints: Each block with HTTP capabilities gets its own path within the app’s URL

You can access these URLs programmatically wherever app and block contexts are available:

const appEndpointUrl = input.app.http.url;
const blockEndpointUrl = input.block.http.url;

When configuring apps, you can use these placeholders in instruction text that will be replaced with actual values:

  • {appEndpointUrl} - The complete endpoint URL (e.g., https://app-123.flows.example.com/)
  • {appEndpointHost} - The hostname of the endpoint (e.g., app-123.flows.example.com)

These placeholders are useful for providing users with specific URLs they need to configure in external services.

The onRequest handler receives an input object containing:

  • App or block context
  • The HTTP request details
interface HTTPRequest {
requestId: string; // Unique ID for this request
path: string; // Request path
method: string; // HTTP method (GET, POST, etc.)
headers: Record<string, string>; // HTTP headers
query: Record<string, string>; // Query parameters
params: Record<string, string>; // Path parameters
rawBody: string; // Raw request body
body: any; // Parsed body (if JSON)
}
onRequest: async ({ request }) => {
// Access request details
console.log(`Method: ${request.method}`);
console.log(`Path: ${request.path}`);
console.log(`Query parameters:`, request.query);
console.log(`Headers:`, request.headers);
// Access the body
if (request.method === "POST") {
console.log("Request body:", request.body);
}
// Send a response
await http.respond(request.requestId, {
statusCode: 200,
headers: {
"Content-Type": "application/json",
},
body: {
message: "Request processed successfully",
},
});
};

To respond to an HTTP request, use the http.respond function:

await http.respond(requestId, response);
  • requestId: The ID of the request to respond to (from the request object)
  • response: The HTTP response to send
interface HTTPResponse {
statusCode?: number; // Default: 200
headers?: Record<string, string>; // Response headers
body?: string | object | unknown; // Response body
}
// Simple JSON response
await http.respond(request.requestId, {
statusCode: 200,
body: { message: "Success", data: result },
});
// HTML response
await http.respond(request.requestId, {
statusCode: 200,
headers: {
"Content-Type": "text/html",
},
body: "<html><body><h1>Hello World</h1></body></html>",
});
// Error response
await http.respond(request.requestId, {
statusCode: 400,
body: { error: "Bad request", details: "Missing required field" },
});
// No content response
await http.respond(request.requestId, {
statusCode: 204,
});

One of the most powerful patterns is using HTTP endpoints to trigger events within Flows. See the Events documentation for more details on the event system.

import { AppBlock, http, events } from "@slflows/sdk/v1";
export const webhookBlock: AppBlock = {
name: "Webhook Receiver",
http: {
onRequest: async (input:): Promise<void> => {
const { request } = input;
// Validate the request
if (request.method !== "POST") {
await http.respond(request.requestId, {
statusCode: 405,
body: { error: "Method not allowed" },
});
return;
}
try {
// Process the webhook payload
const payload = request.body;
// Emit an event with the webhook data
await events.emit(
{
type: "webhook_received",
source: request.headers["user-agent"],
payload,
},
);
// Respond to the webhook sender
await http.respond(request.requestId, {
statusCode: 200,
body: { status: "received" },
});
} catch (error) {
console.error("Error processing webhook:", error);
await http.respond(request.requestId, {
statusCode: 500,
body: { error: "Internal server error" },
});
}
},
},
outputs: {
default: {
name: "Webhook Received",
description: "Emitted when a webhook is received",
},
},
};