---
name: rtie-webhooks-and-events
description: Register webhook endpoints, verify HMAC signatures, and understand the RTIE event catalog for cross-module integrations.
version: "0.1.0"
---

# RTIE Webhooks and Events

RTIE delivers events to your backend via **HMAC-signed webhooks** using Cloudflare Queues for reliable, at-least-once delivery with automatic retry.

## Registering an endpoint

```typescript
// MCP: create_webhook_endpoint
await mcp.callTool("create_webhook_endpoint", {
  url: "https://your-app.com/webhooks/rtie",
  secret: "whsec_yourRandomSecret",    // use crypto.randomBytes(32).toString("hex")
  eventTypes: ["auction.bid.placed", "auction.closed", "room.member.joined"],
});

// Or via SDK
const endpoint = await rtie.webhooks.create({
  url: "https://your-app.com/webhooks/rtie",
  secret: process.env.RTIE_WEBHOOK_SECRET!,
  eventTypes: ["auction.bid.placed"],
});
```

## Listing endpoints and deliveries

```typescript
// MCP: list_webhook_endpoints
await mcp.callTool("list_webhook_endpoints");
// → [{ id, url, eventTypes, enabled, createdAt }, ...]

// MCP: list_webhook_deliveries (check delivery status and retry history)
await mcp.callTool("list_webhook_deliveries", { endpointId: "wh_abc123" });
// → [{ id, eventType, statusCode, deliveredAt, attempts }, ...]
```

## Receiving and verifying events

RTIE signs every delivery with HMAC-SHA256. **Always verify the signature** before processing:

```typescript
// Hono handler
app.post("/webhooks/rtie", async (c) => {
  const body = await c.req.text();
  const sig = c.req.header("rtie-signature");

  if (!verifyRtieSignature(body, sig, process.env.RTIE_WEBHOOK_SECRET!)) {
    return c.json({ error: "Invalid signature" }, 401);
  }

  const event = JSON.parse(body) as RtieWebhookEvent;
  await handleEvent(event);
  return c.json({ received: true });
});

function verifyRtieSignature(payload: string, sig: string | undefined, secret: string): boolean {
  if (!sig) return false;
  const [timestamp, hash] = sig.split(",");
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.${payload}`)
    .digest("hex");
  return crypto.timingSafeEqual(Buffer.from(hash), Buffer.from(expected));
}
```

## Event payload shape

```typescript
interface RtieWebhookEvent {
  id: string;               // "evt_abc123"
  type: string;             // e.g. "auction.bid.placed"
  tenantId: string;
  createdAt: string;        // ISO 8601
  data: Record<string, unknown>;   // event-specific payload
}
```

## Event catalog

### Rooms module
| Event | When |
|---|---|
| `room.created` | A new room is created |
| `room.activated` | Room opened for participants |
| `room.closed` | Room session ended |
| `room.member.joined` | A participant joined |
| `room.member.left` | A participant left |

### Auctions module
| Event | When |
|---|---|
| `auction.created` | New auction created |
| `auction.started` | Bidding opened |
| `auction.bid.placed` | A bid was placed |
| `auction.outbid` | Bidder was outbid |
| `auction.closed` | Auction ended, winner determined |
| `auction.buy_now.completed` | Buy-now purchase completed |

### Chat module
| Event | When |
|---|---|
| `chat.message.sent` | Message sent in a room |
| `chat.message.deleted` | Message removed |

### Payments module
| Event | When |
|---|---|
| `payment.order.created` | Order initiated |
| `payment.order.paid` | Payment confirmed |
| `payment.order.failed` | Payment failed |
| `payment.payout.sent` | Stripe Connect payout to seller |

### Actions module
| Event | When |
|---|---|
| `action.signal.ingested` | Signal received and queued |
| `action.rule.fired` | A rule matched and action dispatched |

## Retry policy

RTIE retries failed deliveries with exponential backoff:
- Attempt 1: immediate
- Attempt 2: 30 seconds
- Attempt 3: 5 minutes
- Attempt 4: 30 minutes
- Attempt 5: 6 hours

After 5 failures, the delivery is marked as failed and recorded in `list_webhook_deliveries` for manual inspection.

## Idempotency

Each event has a unique `id`. Always store processed event IDs and skip duplicates:

```typescript
const processedEvents = new Set<string>();

async function handleEvent(event: RtieWebhookEvent) {
  if (processedEvents.has(event.id)) return;   // idempotency check
  processedEvents.add(event.id);

  switch (event.type) {
    case "auction.closed":
      await notifySeller(event.data);
      break;
    // ...
  }
}
```

Full docs: https://docs.rtie.ai/modules/webhooks
