Webhook Endpoints
Webhook endpoints in Obelisk provide a way to trigger executions via incoming HTTP requests. They act as entry points into your Obelisk system, allowing external services and applications to interact with your workflows and activities.
Key Concepts
-
HTTP-Triggered Execution: Webhook endpoints are fundamentally different from workflows in how they are initiated. Workflows are started explicitly (CLI, UI, gRPC, or scheduled), while webhook endpoints are triggered by incoming HTTP requests.
-
Request Handling: A webhook endpoint is defined by a Rust function that receives an HTTP
Request
object (from thewaki
crate) and returns an HTTPResponse
object. This function can then process the request data (headers, body, query parameters) and perform actions accordingly. -
Limited Concurrency: Unlike general workflows, webhook endpoints do not support join sets . Within a webhook endpoint, you can either:
- Directly Call a Workflow/Activity Function: This is similar to a direct call within a workflow. The webhook endpoint function will block until the called function completes and returns a result. This result can then be used to construct the HTTP response.
- Schedule a Workflow/Activity Function: Use the
-schedule
extension function to schedule a workflow or activity for later execution. This is a "fire-and-forget" operation from the perspective of the webhook endpoint. The endpoint will not wait for the scheduled execution to complete, and the result of the scheduled function is not available to the webhook.
-
Single-Threaded Execution: Like workflows, webhook endpoint code executes in a single-threaded, synchronous manner within its WebAssembly sandbox. You don't need to manage asynchronous operations directly within your webhook handler function.
-
Environment Variables: You must extract parameters from the environment variables. The names of the variables are capitalized names of the HTTP URL path parameters.
Creating a Webhook Endpoint
-
Definition: Define a Rust function that will handle incoming HTTP requests. If the function is annotated with the
#[handler]
macro from the waki crate, the macro handles the necessary WASI 0.2 HTTP imports and exports. -
WIT Imports: Similar to workflows, in order to call workflows or activities, you need to import their WIT interfaces.
-
Compilation to WASM: The Rust code is compiled into a WebAssembly Component.
-
Deployment: Create a
[[webhook_endpoint]]
table in yourobelisk.toml
Obelisk configuration file. This table specifies:name
: A unique name for the endpoint.http_server
: An association to ahttp_server
location
: A table that defines where to load the wasm component from. It can be eitherlocation.path
to a local file orlocation.oci
to use a OCI registry.routes
: A request matching any of the specified routes will be forwarded to this endpoint. See next sections for details.
-
Execution: When an HTTP request is made to a matching route, Obelisk:
- Loads the associated WebAssembly Component.
- Invokes the handler function, passing in the request and path parameters.
- The handler function executes, potentially calling workflows/activities directly or scheduling them.
- The response returned by the handler function is sent back to the client that made the HTTP request.
Example - triggering the Fibonacci workflow
This example demonstrates a webhook endpoint that triggers the example Fibonacci workflow . It showcases both direct calls and scheduled execution of a workflow function.
WIT File (wit/template_fibo.wit
):
package any:any;
world any {
import template-fibo:workflow/fibo-workflow-ifc;
import template-fibo:workflow-obelisk-ext/fibo-workflow-ifc;
import obelisk:log/log@1.0.0;
}
Rust Code (src/lib.rs
):
#[handler]
fn handle(_req: Request) -> Result<Response, ErrorCode> {
let n = std::env::var("N")
.expect("env var `N` must be set by Obelisk if routes are configured properly")
.parse()
.expect("parameter `N` must be of type u8");
let iterations = std::env::var("ITERATIONS")
.expect("env var `ITERATIONS` must be set by Obelisk if routes are configured properly")
.parse()
.expect("parameter `ITERATIONS` must be of type u32");
let fibo_res = if n >= 10 {
println!("scheduling");
let execution_id = workflow_ext::fiboa_schedule(ScheduleAt::Now, n, iterations);
format!("scheduled: {}", execution_id.id)
} else if n > 1 {
// Call the execution directly.
println!("direct call");
let fibo_res = workflow::fiboa(n, iterations);
format!("direct call: {fibo_res}")
} else {
println!("hardcoded");
"hardcoded: 1".to_string() // For performance testing - no activity is called
};
info(&format!("Sending response {fibo_res}"));
Response::builder().body(fibo_res).build()
}
obelisk.toml
(Excerpt):
[[http_server]]
name = "external"
listening_addr = "0.0.0.0:9000"
[[webhook_endpoint]]
name = "fibo"
location.path = "target/wasm32-wasip2/release/fibo_webhook_endpoint.wasm" # Or use location.oci
http_server = "external"
routes = [{ methods = ["GET"], route = "/fibo/:N/:ITERATIONS" }] # Path parameters are captured
Project Template:
You can quickly create a new webhook endpoint project using cargo-generate and the webhook template:
cargo generate obeli-sk/obelisk-templates fibo/webhook_endpoint --name mywebhook
cd mywebhook
cargo build --release
obelisk server run
Then, you can test it with curl
:
curl localhost:9000/fibo/5/1 # Direct call
curl localhost:9000/fibo/12/1 # Scheduled execution
Example - Stargazers demo
The webhook endoint in stargazers demo repository shows the integration with GitHub, specifically:
- Setting a shared secret and passing it as an environment variable
- Verifying the request signature using HMAC
- Creating a tunnel to expose the local HTTP server
- Testing
Key Differences from Workflows
Feature | Workflows | Webhook Endpoints |
---|---|---|
Triggering | Explicit - CLI, Web UI, gRPC | Incoming HTTP Request |
Function Extensions | Full support (join sets) | Limited to direct calls or scheduling |
Purpose | Complex orchestration, long-running processes | Handling HTTP requests, typically just triggering a workflow |
Parameters | Explicitly declared in WIT | URL path parameters, configured environment variables |
Configuration
Associating Webhook Endpoints with HTTP Servers
Webhook endpoints don't listen for HTTP requests directly. Instead, they are associated with an [[http_server]]
instance defined in your obelisk.toml
configuration. This association is established using the http_server
field within the [[webhook_endpoint]]
configuration.
[[http_server]]
name = "my_server"
listening_addr = "0.0.0.0:9000"
[[webhook_endpoint]]
name = "my_endpoint"
http_server = "my_server" # Associates this endpoint with the "my_server" HTTP server
# ... other webhook endpoint settings ...
Routes Configuration
Each [[webhook_endpoint]]
must define one or more routes. These routes determine which incoming HTTP requests will be handled by that endpoint. The routes field is a list of route specifications.
[[webhook_endpoint]]
name = "my_endpoint"
http_server = "my_server"
routes = [
{ methods = ["GET"], route = "/users/{id}" }, # Route with method
"/products/*", # Route without method (wildcard)
"/status", # Route without method (exact match)
]
# ... other webhook endpoint settings ...
Route Syntax and Matching
Obelisk supports several ways to define routes:
-
Static Routes: Match exact URL paths.
-
"/"
: Matches only the root path. -
"/path"
: Matches the exact path/path
.
-
-
Wildcard Routes: Match path prefixes.
"/path/*"
: Matches any path that starts with/path/
, including/path/
,/path/subpath
,/path/a/b/c
, etc.
-
Parameterized Routes: Capture parts of the path as parameters.
"/status/:PARAM1/:PARAM2"
: Matches paths like/status/123/abc
. The values123
andabc
will be available to the webhook endpoint as environment variables namedPARAM1
andPARAM2
, respectively.
-
Method-Specific Routes: These routes combine an HTTP method (or methods) with a path. Method-specific routes have higher priority than routes without methods.
-
{ methods = ["GET"], route = "/path/*" }
: A wildcard route restricted to onlyGET
requests. -
{ methods = ["POST", "PUT"], route = "/resource" }
: A static route restricted toPOST
orPUT
requests.
-
-
Matching All Paths
""
or"/*"
- Match all paths
Route Priority and Matching Order
When an incoming HTTP request arrives, Obelisk checks the configured routes to determine which webhook endpoint should handle it. The matching process follows these rules:
-
Method-Specific Routes First: Obelisk first tries to match the request against routes that specify HTTP methods (e.g., { methods = ["GET"], route = "/path" }).
-
First Match Wins (Within Priority): Within each priority level (method-specific or not), the first route that matches the request wins. The order of routes in the routes list matters.