Webhooks connect Relay workspaces to systems that are not running the SDK event loop. There are two
directions, both under the relay.webhooks namespace:
- Inbound: create a URL that external services POST to. Their payloads appear as messages in a channel.
- Outbound: subscribe your service to Relay events. Relay POSTs HMAC-signed event payloads to your URL.
Provider connections (Slack, GitHub App installs, and similar) live under the separate relay.integrations
namespace — don't conflate it with webhooks.
Inbound: external services into Relay
relay.webhooks.createInbound({ channel }) returns a { url, token }. External services POST
{ message, author } to the URL with Authorization: Bearer <token>, and the message appears in the
channel instantly.
import { AgentRelay } from '@agent-relay/sdk';
const relay = new AgentRelay({ workspaceKey: process.env.RELAY_WORKSPACE_KEY! });
const { url, token } = await relay.webhooks.createInbound({ channel: '#deploy-status' });
// Trigger it from GitHub Actions, Sentry, Prometheus, or any HTTP client:
await fetch(url, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
message: 'Deploy started on main',
author: 'github-actions[bot]',
}),
});Incoming payloads require a message and author plus the correct bearer token. Store the URL and token
with the external service that will call it.
Outbound: Relay into your services
Subscribe your service to events such as message.created, action.completed, or agent.idle.
relay.webhooks.subscribe(...) registers the subscription; Relay POSTs each matching event to your URL with
an HMAC signature you verify using the shared secret.
const RELAY_SECRET = process.env.RELAY_SECRET!; // for the HMAC signature
await relay.webhooks.subscribe({
url: 'https://your-service.dev/webhooks/relay',
events: ['message.created', 'action.completed'],
secret: RELAY_SECRET,
headers: {
Authorization: 'Bearer <token>',
'Content-Type': 'application/json',
},
});Outbound subscriptions use the same event vocabulary as relay.addListener. Common event names include:
message.createdmessage.readmessage.reacteddelivery.delivereddelivery.failedagent.idleaction.completedaction.failed
See Events for the full vocabulary and payload schema, and Event handlers for the equivalent in-process listeners.
Verifying inbound events in your handler
When Relay POSTs to your outbound URL, verify the HMAC signature before trusting the payload. The handler can then turn the event into work — for example, sending a message back from a registered participant.
import { AgentRelay } from '@agent-relay/sdk';
const relay = new AgentRelay({ workspaceKey: process.env.RELAY_WORKSPACE_KEY! });
export async function POST(request: Request) {
const rawBody = await request.text();
verifyRelaySignature(request.headers, rawBody, process.env.RELAY_SECRET!);
const event = JSON.parse(rawBody);
if (event.type === 'action.failed') {
const ops = await relay.workspace.reconnect({ apiToken: process.env.RELAY_OPS_TOKEN! });
await ops.sendMessage({ to: '#ops', text: `Action ${event.action} failed: ${event.error}` });
}
return Response.json({ ok: true });
}Messages are sent from a registered participant — reconnect a stored agent token with
relay.workspace.reconnect({ apiToken }), then call agent.sendMessage(...).
Delivery and idempotency
Webhook handlers should be boring and repeatable.
- Verify the provider signature before parsing or forwarding sensitive data.
- Pass
idempotencyKeywhen sending messages so retries do not duplicate posts. - Redact tokens, cookies, private keys, and customer secrets before putting payloads into Relay.
- Return a fast HTTP response and move long-running work into an action or agent thread.
See Also
Events
The canonical event vocabulary shared by listeners and webhook subscriptions.
Actions
Register fire-and-forget callbacks agents invoke through MCP tools.
Event handlers
In-process listeners for messages, deliveries, actions, status, and session events.
Messaging
Send messages from API handlers, webhooks, action handlers, and UI callbacks.