# How to Register Functions and Triggers with iii WebSocket Protocol Messages

> Learn how to register functions and triggers with the iii WebSocket protocol using registerfunction and registertrigger messages. Easily declare schemas and bind triggers via the SDK.

- Repository: [iii/iii](https://github.com/iii-hq/iii)
- Tags: how-to-guide
- Published: 2026-05-28

---

**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`](https://github.com/iii-hq/iii/blob/main/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.

```json
{
  "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`](https://github.com/iii-hq/iii/blob/main/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`.

```json
{
  "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`](https://github.com/iii-hq/iii/blob/main/engine/src/engine/mod.rs).

## Registering Functions with the Rust SDK

The `III::register_function` method in [`sdk/packages/rust/iii/src/iii.rs`](https://github.com/iii-hq/iii/blob/main/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.

```rust
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.

```rust
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`](https://github.com/iii-hq/iii/blob/main/sdk/packages/rust/iii/src/iii.rs). The `IIITrigger` enum in [`sdk/packages/rust/iii/src/builtin_triggers.rs`](https://github.com/iii-hq/iii/blob/main/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`.

```rust
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.

```rust
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`](https://github.com/iii-hq/iii/blob/main/sdk/packages/rust/iii/src/iii.rs), the `RegisterFunction` builder offers a `.metadata()` method, while `RegisterTriggerInput` (created via `IIITrigger`) also supports `.metadata()`.

```rust
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`](https://github.com/iii-hq/iii/blob/main/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`](https://github.com/iii-hq/iii/blob/main/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`](https://github.com/iii-hq/iii/blob/main/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`](https://github.com/iii-hq/iii/blob/main/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`.