How to Register Functions and Triggers with iii WebSocket Protocol Messages

Use the registerfunction and registertrigger JSON message types via the SDK's III::register_function and III::register_trigger methods to declare function schemas and bind triggers over the WebSocket connection.

The iii engine communicates with SDK clients over a JSON-based WebSocket protocol. When building applications with the iii-hq/iii repository, you register capabilities by sending specific protocol messages that tell the engine how to route and invoke your code. This article covers the registerfunction and registertrigger message structures and how to generate them using the Rust SDK.

Understanding the iii WebSocket Protocol Messages

The protocol defines distinct message types for declaring functions versus binding them to event sources. Both are serialized to JSON and sent as WebSocket frames.

The registerfunction Message Structure

Defined in sdk/packages/rust/iii/src/protocol.rs, the RegisterFunctionMessage struct represents the registerfunction message type. It contains the function id, optional description, JSON Schema definitions for request_format and response_format, arbitrary metadata, and optional HTTP invocation configuration for external handlers.

{
  "type": "registerfunction",
  "id": "my::function",
  "description": "optional description",
  "request_format": { "type": "object" },
  "response_format": { "type": "object" },
  "metadata": { "team": "payments" },
  "invocation": {
    "url": "https://example.com/handler",
    "method": "POST",
    "timeout_ms": 30000,
    "headers": {},
    "auth": null
  }
}

The invocation field is only present for HTTP-invoked functions. When omitted, the engine expects a local handler to exist in the SDK process.

The registertrigger Message Structure

Also defined in sdk/packages/rust/iii/src/protocol.rs, the RegisterTriggerMessage creates a registertrigger frame. It links a trigger type—such as cron, http, or queue—to a previously registered function via function_id, including trigger-specific config and optional metadata.

{
  "type": "registertrigger",
  "id": "auto-generated-uuid",
  "trigger_type": "cron",
  "function_id": "my::function",
  "config": {
    "expression": "0 0 9 * * * *"
  },
  "metadata": { "priority": "high" }
}

The engine validates the function_id reference and stores the binding in internal maps located in engine/src/engine/mod.rs.

Registering Functions with the Rust SDK

The III::register_function method in sdk/packages/rust/iii/src/iii.rs accepts a builder that constructs and sends the protocol message automatically over the WebSocket connection.

Local Handler Functions

Use RegisterFunction::new to create functions that execute within the worker process. The SDK extracts JSON Schema from your handler's types and sends the registerfunction message.

use iii_sdk::{III, RegisterFunction};

let iii = III::new(...);
iii.register_function(
    "my::echo",
    RegisterFunction::new(|payload: serde_json::Value| {
        Ok(payload)
    })
).expect("function registration failed");

The builder creates a RegisterFunctionMessage that becomes a Message::RegisterFunction variant, serialized and transmitted by the SDK's WebSocket client.

HTTP-Invoked Functions

For external handlers, populate the invocation field using HttpInvocationConfig. This creates a registerfunction message that instructs the engine to forward invocations to a remote URL rather than the local handler process.

use iii_sdk::{III, RegisterFunction, HttpInvocationConfig, HttpMethod};

let iii = III::new(...);
let http_cfg = HttpInvocationConfig {
    url: "https://my-lambda.example.com/invoke".into(),
    method: HttpMethod::Post,
    timeout_ms: Some(30000),
    headers: std::collections::HashMap::new(),
    auth: None,
};

iii.register_function(
    "external::my_lambda",
    RegisterFunction::http(http_cfg)
).expect("HTTP function registration failed");

The RegisterFunction::http builder variant sets the invocation field, causing the engine to route calls to the specified endpoint instead of expecting a local execution.

Binding Triggers to Functions

Triggers are registered separately using III::register_trigger, defined in sdk/packages/rust/iii/src/iii.rs. The IIITrigger enum in sdk/packages/rust/iii/src/builtin_triggers.rs provides typed builders that generate RegisterTriggerInput, which the SDK converts to registertrigger messages.

Cron Triggers

The IIITrigger::Cron variant accepts a CronTriggerConfig with a cron expression. Calling .for_function() binds it to a specific function ID and returns a RegisterTriggerInput.

use iii_sdk::{III, RegisterTriggerInput, builtin_triggers::CronTriggerConfig};

let iii = III::new(...);
let trigger_input = IIITrigger::Cron(
    CronTriggerConfig::new("0 0 9 * * * *")
).for_function("external::my_lambda");

iii.register_trigger(trigger_input)
    .expect("cron trigger registration failed");

The SDK serializes this to a registertrigger frame with trigger_type set to "cron" and the expression inside the config object.

HTTP Endpoint Triggers

Use IIITrigger::Http with HttpTriggerConfig to expose functions via HTTP paths. The config field includes the route, methods, and optional middleware chain.

use iii_sdk::{III, RegisterTriggerInput, builtin_triggers::HttpTriggerConfig, HttpMethod};

let iii = III::new(...);
let http_trigger = IIITrigger::Http(
    HttpTriggerConfig::new("/webhook")
        .method(HttpMethod::Post)
        .middleware_function_ids(vec!["middleware::auth".into()])
).for_function("my::echo");

iii.register_trigger(http_trigger).expect("HTTP trigger registration failed");

The engine uses this configuration to route incoming HTTP requests matching the path and method to the specified function.

Adding Metadata to Registrations

Both functions and triggers support arbitrary metadata objects stored verbatim by the engine. In sdk/packages/rust/iii/src/iii.rs, the RegisterFunction builder offers a .metadata() method, while RegisterTriggerInput (created via IIITrigger) also supports .metadata().

use serde_json::json;

// Function metadata
let func = RegisterFunction::new(|v| Ok(v))
    .metadata(json!({ "team": "payments", "version": "v1" }));

iii.register_function("payments::process", func).unwrap();

// Trigger metadata
let trigger = IIITrigger::Cron(CronTriggerConfig::new("0 */5 * * * *"))
    .for_function("payments::process")
    .metadata(json!({ "priority": "high" }));

iii.register_trigger(trigger).unwrap();

Metadata is stored alongside the registration and can be queried by the engine or other services for observability and routing decisions.

Summary

  • The iii protocol uses registerfunction and registertrigger JSON messages sent over WebSocket to declare capabilities and event bindings.
  • RegisterFunctionMessage and RegisterTriggerMessage in sdk/packages/rust/iii/src/protocol.rs define the schema for these messages.
  • Use III::register_function with RegisterFunction builders to create local handlers or HTTP-invoked functions.
  • Use III::register_trigger with IIITrigger variants like Cron and Http to bind event sources to functions.
  • The engine receives these messages via engine/src/engine/mod.rs and stores them in internal maps for routing incoming invocations.

Frequently Asked Questions

What is the difference between registerfunction and registertrigger?

registerfunction declares a function's existence, input/output schema, and execution location (local or remote HTTP endpoint), while registertrigger binds a specific event source—such as a cron schedule, HTTP endpoint, or queue—to that function. You must register the function with a unique ID before you can bind triggers to it.

How does the iii engine handle HTTP-invoked functions differently from local handlers?

When the invocation field is present in the registerfunction message, the engine treats the registration as an external function and forwards invocation requests to the specified URL via HTTP. When the field is absent, the engine expects the SDK to provide a local handler and dispatches executions to the connected WebSocket client.

Where are the protocol message definitions located in the iii repository?

The message structs RegisterFunctionMessage and RegisterTriggerMessage are defined in sdk/packages/rust/iii/src/protocol.rs. The SDK converts these into JSON frames before transmission. The engine-side handlers that process incoming registerfunction and registertrigger frames are implemented in engine/src/engine/mod.rs.

Can I register multiple triggers for the same function?

Yes. Each trigger requires a separate registertrigger message with a unique trigger ID. You can bind multiple cron schedules, HTTP endpoints, or queue sources to a single function by calling III::register_trigger multiple times with different configurations, each referencing the same function_id.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →