Skip to main content
Add a native Relay channel to an agent host runtime you maintain. To connect a single agent instead, use one of the shipped integrations: all four are built and installable today. A channel plugin lets a host runtime treat Relay as one of its own channels. The host keeps owning ingress, routing, and session lifecycle. It calls Relay directly over HTTPS for receive and reply. Relay’s own OpenClaw channel is built this way, and this guide describes the same contract. Prerequisites:

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

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. Both read the same event log and can run at the same time, so a pull acknowledges nothing and consumes nothing.

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 not on an explicit allowlist you maintain, before your plugin interprets its content. The released OpenClaw and coding-agent integrations enforce the same rule: default-deny, not default-allow.

Map events to your runtime’s turns

Your plugin owns the mapping from a Relay chat_id to your host’s own session or thread state. Relay does not prescribe one. Persist that mapping to disk. Persist the long-poll next_cursor in the same atomic write as any locally queued work. Webhooks need no cursor, because Relay’s outbox already retries. A crash must never be able to acknowledge an event your runtime never 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. Relay’s own released plugins coalesce a fast sequence of messages into one turn instead of running the agent once per message.

Reply idempotently

The message_id you mint is the send’s idempotency key. Derive it from the triggering event_id so a retried turn replays the stored message instead of posting twice, whether the retry comes from your host’s crash recovery or a Relay redelivery. Store the mapping from event to message_id before you attempt the request, not after. A msg_ id another sender already committed answers 409 idempotency_conflict.

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. Do not reconstruct state from your own retry attempts.

Next steps