Skip to main content
A channel plugin lets a host runtime, such as OpenClaw, treat Relay as one of its own channels: the host keeps owning ingress, routing, and session lifecycle, and calls Relay directly over HTTPS for receive and reply. Build one when you maintain an agent runtime and want Relay users to reach agents that already run inside it. Prerequisites: an agent and its Agent Token (Create and connect an agent), and a read of Delivery model and Webhooks.

Choose this pattern deliberately

Relay’s public contract supports more than one integration shape. Pick the narrowest one that fits. A channel plugin is the right shape only when the host runtime, not Relay, decides which local agent handles a turn.

Pick a transport

Signed webhooks and long polling are mutually exclusive per Agent Token. Enabling one disables the other; polling while a webhook is active returns 409 conflict.
Use signed webhooks when your host already terminates public HTTPS. Use long polling when it does not, such as a channel plugin running inside a user’s own process. Run exactly one consumer per Agent Token; a newer poll or webhook registration takes over and the older one gets terminated_by_other_consumer.

Scope every inbound turn to an owner

Call GET /v1/agents/me once at startup and pin the returned owner user id. Drop any inbound message whose sender is not the owner (or an explicit allowlist you maintain) before your plugin interprets its content. This is the same rule the released OpenClaw and coding-agent integrations enforce: default-deny, not default-allow.

Map events to your runtime’s turns

Your plugin owns the mapping from a Relay conversation_id to your host’s own session or thread state; Relay does not prescribe one. Persist that mapping durably, and persist the long-poll next_cursor (or nothing, for webhooks, since Relay’s outbox already retries) in the same atomic write as any locally queued work. A crash must never be able to acknowledge an event your runtime never actually queued. Deduplicate on event_id regardless of transport: both webhooks and long-poll redeliver at least once. If your host processes a rapid burst of messages as separate turns, consider a short debounce window before starting work, the way Relay’s own released plugins coalesce a fast sequence of messages into one turn instead of running the agent once per message.

Reply idempotently

Derive every outbound Idempotency-Key from the triggering event_id. A retried turn, whether from your host’s own crash recovery or a Relay redelivery, then replays the original send instead of posting twice. Store the mapping from event to idempotency key before you attempt the request, not after.

Handle failure without losing state

Follow the recovery rule: conversation history is the source of truth for what a thread contains. After a crash, a 401, or any ambiguous response, reconcile from conversation history rather than reconstructing state from your own retry attempts.

Next steps