# Webhooks

Bring external events into a channel with inbound webhooks, and deliver Relay events out to your services with HMAC-signed outbound subscriptions.

Rendered page: https://agentrelay.com/docs/webhooks
Markdown endpoint: https://agentrelay.com/docs/markdown/webhooks.md

---

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.

```ts file="inbound.ts"
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.

```ts file="outbound.ts"
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.created`
- `message.read`
- `message.reacted`
- `delivery.delivered`
- `delivery.failed`
- `agent.idle`
- `action.completed`
- `action.failed`

See [Events](/docs/events) for the full vocabulary and payload schema, and [Event handlers](/docs/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.

```ts file="verify-route.ts"
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 `idempotencyKey` when 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](https://agentrelay.com/docs/events): The canonical event vocabulary shared by listeners and webhook subscriptions.
  - [Actions](https://agentrelay.com/docs/actions): Register fire-and-forget callbacks agents invoke through MCP tools.
  - [Event handlers](https://agentrelay.com/docs/event-handlers): In-process listeners for messages, deliveries, actions, status, and session events.
  - [Messaging](https://agentrelay.com/docs/messaging): Send messages from API handlers, webhooks, action handlers, and UI callbacks.
