# CoffeeRelay — Complete Documentation > CoffeeRelay is a webhook relay service built for AI agents and event-driven workflows. It provides stable ingest URLs, multi-destination fan-out, signature verification, replay, dead-letter queuing, circuit breakers, and stateful session storage for AI agent continuity. **Operated by:** Last Myle, LLC **Website:** https://coffeerelay.dev **Support:** support@coffeerelay.dev **Hosting:** Vercel (app) + Neon PostgreSQL (database) **Auth:** GitHub OAuth (dashboard), API keys coming in Phase 1 --- ## Table of Contents 1. [Getting Started](#getting-started) 2. [Core Concepts](#core-concepts) 3. [AI Agent Features](#ai-agent-features) 4. [Ingest API](#ingest-api) 5. [Management API](#management-api) 6. [Signature Verification](#signature-verification) 7. [Claude Code Integration](#claude-code-integration) 8. [Plans & Pricing](#plans-and-pricing) 9. [For AI Agents — Setup Guide](#for-ai-agents--setup-guide) --- ## Getting Started ### Current Authentication Status **Dashboard Access:** GitHub OAuth at https://coffeerelay.dev/sign-in **API Keys:** ✅ Now available! Bearer token authentication for programmatic access. **Public Ingest:** The webhook ingest endpoint (`/api/in/{slug}`) is public and requires no authentication. ### Quick Start with API Keys 1. **Create API key** — Sign in → Dashboard → Settings → Create Key 2. **Bootstrap endpoint** — One API call creates endpoint + destination: ```bash curl -X POST https://coffeerelay.dev/api/agent/bootstrap \ -H "Authorization: Bearer cr_live_YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{"destinationUrl": "https://your-agent.com/webhook"}' ``` 3. **Register ingest URL** — Use returned `ingestUrl` in Stripe/GitHub/etc 4. **Verify signatures** — Use returned `signingSecret` to verify webhooks ### Quick Start (Manual Setup) 1. **Sign up** — Create account at https://coffeerelay.dev/sign-in (GitHub OAuth) 2. **Create endpoint** — Go to Dashboard → Endpoints → New Endpoint 3. **Add destination(s)** — Enter URL(s) where webhooks should be forwarded 4. **Copy ingest URL** — Use `https://coffeerelay.dev/api/in/{your-slug}` in external services 5. **Test** — Send a webhook via curl or configure in Stripe/GitHub/etc. ### Test with curl ```bash curl -X POST https://coffeerelay.dev/api/in/your-slug \ -H "Content-Type: application/json" \ -d '{"event": "test", "data": "hello"}' ``` Response: ```json { "received": true, "logId": "clx...", "destinations": 2 } ``` --- ## Core Concepts ### Endpoints An **endpoint** is a unique ingest URL. Each endpoint has: - **Slug** (URL-safe identifier, auto-generated) - **Signing secret** (for HMAC verification, auto-generated) - **Destinations** (1+ URLs where webhooks are forwarded) - **Active/inactive toggle** Ingest URL format: `https://coffeerelay.dev/api/in/{slug}` ### Destinations A **destination** is a target URL that receives forwarded webhooks. Each endpoint can fan out to multiple destinations in parallel. **Use cases:** - Forward to production + staging + local dev tunnel - Fan out GitHub webhooks to multiple microservices - Send Stripe events to analytics + fulfillment + support systems ### Webhook Delivery Flow 1. External service POSTs to ingest URL 2. CoffeeRelay logs the webhook (method, headers, body, source IP) 3. Checks idempotency key (if Starter/Pro) 4. Extracts session_id (if present and Starter/Pro) 5. Forwards to all active destinations **in parallel** 6. Adds `x-coffeerelay-signature` header to each forwarded request 7. Logs delivery attempts (status, latency, response body up to 4KB) 8. Failed deliveries trigger alerts (if configured) and/or DLQ (if Pro) **Stripped headers (hop-by-hop):** ``` host, connection, keep-alive, transfer-encoding, te, trailer, upgrade, proxy-authorization, proxy-authenticate ``` --- ## AI Agent Features ### 1. Session State Storage (Starter/Pro) **Problem:** Webhooks are stateless. AI agents need context across multiple events. **Solution:** CoffeeRelay stores session state and hydrates it into forwarded webhooks. **How it works:** Send webhooks with session identifier: - Header: `x-session-id: user-123-workflow-abc` - Or JSON body: `{ "session_id": "user-123-workflow-abc", ... }` CoffeeRelay automatically: 1. Extracts session_id from header or body 2. Looks up stored state for that session + endpoint 3. Injects state into forwarded webhook headers: - `x-session-id: user-123-workflow-abc` - `x-session-state: {"step": "payment", "data": {...}}` - `x-session-metadata: {"userId": "123"}` - `x-correlation-id: trace-xyz` (if set) **Create/Update Sessions:** Currently via dashboard Sessions tab. API coming in Phase 1. **Use cases:** - Multi-step agent workflows (order created → payment success → fulfillment) - Chatbot conversation history triggered by webhooks - Progressive form submissions with state persistence - Approval chains spanning multiple webhook events **Limits:** - Starter: 50 sessions per endpoint, 24h TTL - Pro: 500 sessions per endpoint, 72h TTL --- ### 2. Circuit Breaker (Starter/Pro) **Problem:** Agent hits an error, retries endlessly, racks up expensive LLM API calls. **Solution:** Automatic circuit breaker at webhook layer. **How it works:** When error rate exceeds threshold (configurable, default 50% over 10 min): 1. Circuit opens 2. Endpoint auto-pauses (if configured) 3. Alert sent via Slack/PagerDuty/email (if configured) 4. Future webhook deliveries return 503 **Recovery:** Manual reset via dashboard "Reset Circuit" button or automatic after cooldown. **Configuration:** Dashboard → Endpoint → Agent Health tab → Alert Settings: - Error threshold % (default 50%) - Time window (default 10 min) - Minimum volume for alert (default 5 requests) - Auto-pause on circuit break (default true) - Notification channels **Metrics:** - Requests (24h) - Error rate % - Success rate % - Average latency - Circuit state: CLOSED (healthy) | OPEN (paused) | HALF_OPEN (testing) --- ### 3. Dead Letter Queue (Pro) **Problem:** Webhook events silently disappear during deploys or outages. **Solution:** Failed deliveries captured in queryable DLQ with bulk replay. **Auto-retry schedule:** - 1m → 5m → 30m → 2h → 24h - After 5 failed attempts, moves to "discarded" status **Manual actions:** - Replay individual item - Bulk replay all pending (incident recovery mode) - Discard item **Limits:** Pro plan only --- ### 4. Idempotency Keys (Starter/Pro) **Problem:** Webhook providers use at-least-once delivery. Duplicates trigger duplicate LLM calls. **Solution:** Built-in deduplication with 24h window. **How it works:** Send webhook with idempotency key: - Header: `idempotency-key: evt_123abc` - Or: `x-idempotency-key: evt_123abc` - Or: `x-webhook-id: evt_123abc` If CoffeeRelay sees the same key within 24h: ```json { "received": true, "duplicate": true, "originalLogId": "clx..." } ``` Destinations are NOT called. Original response is returned. **Limits:** Starter/Pro only, 24h dedup window --- ### 5. Policy Rules for Claude Code (Starter/Pro) **Problem:** Coding agents can make destructive changes via tool use. **Solution:** Policy enforcement at webhook layer using Claude Code HTTP hooks. **Setup:** 1. Create endpoint with policy rules 2. Configure Claude Code HTTP hooks to use `/api/hooks/evaluate/{slug}` (instead of ingest URL) 3. Rules evaluated before tool execution 4. Block destructive commands, file writes, network calls **Rule Presets:** - Block destructive commands: `rm -rf`, `sudo rm` - Block writes to .env files - Block network calls: `curl`, `wget`, `nc` - Read-only mode: deny all Edit/Write tools **Rule fields:** - Tool pattern (regex): `Bash|Shell|Edit|Write` - Field path: `command`, `file_path`, `content`, `pattern` - Value pattern (regex): `rm\s+-rf|sudo\s+rm` - Action: allow | deny - Priority: higher number = evaluated first **Limits:** - Free: 0 rules (view-only) - Starter: 5 rules per endpoint - Pro: unlimited --- ### 6. Agent Health Monitoring (Starter/Pro) Real-time dashboard showing: - Circuit breaker status - Error rate, success rate, request volume - DLQ pending/retrying/discarded counts - Recent alerts - Average latency (p50) **Alert channels:** - Slack webhook - PagerDuty - Email **Limits:** - Free: No alerting - Starter: 1 alert channel - Pro: 5 alert channels --- ### 7. Signature Verification & Code Generation **Every forwarded webhook is signed:** ``` x-coffeerelay-signature: t=1234567890,v1=5257a869e7... ``` **Verification (Node.js):** ```javascript import { createHmac, timingSafeEqual } from "crypto"; function verifySignature(header, rawBody, secret) { const [tPart, vPart] = header.split(","); const timestamp = tPart.replace("t=", ""); const signature = vPart.replace("v1=", ""); const payload = `${timestamp}.${rawBody}`; const expected = createHmac("sha256", secret) .update(payload) .digest("hex"); return timingSafeEqual( Buffer.from(signature), Buffer.from(expected) ); } ``` **Code Generator (Dashboard):** Endpoint detail page includes code-gen for webhook handlers in multiple frameworks: - TypeScript: Express, Next.js - Python: Flask, FastAPI - Stripe, GitHub, Shopify, Clerk provider-specific patterns **Universal signature verification API:** Dashboard includes built-in verifier for all major providers. --- ## Ingest API ### Receive Webhook (Public) **Endpoint:** `POST | PUT | PATCH | DELETE https://coffeerelay.dev/api/in/{slug}` **Authentication:** None required (public endpoint) **Request:** - Any headers and body - Forwarded as-is to destinations (minus hop-by-hop headers) **Optional Headers:** - `idempotency-key` or `x-idempotency-key`: Deduplication (Starter/Pro) - `x-session-id` or `session-id`: Session state hydration (Starter/Pro) - `x-correlation-id`: Correlation tracking **Response:** Success (200): ```json { "received": true, "logId": "clx...", "destinations": 2 } ``` Duplicate (200): ```json { "received": true, "duplicate": true, "originalLogId": "clx..." } ``` Endpoint not found (404): ```json { "error": "Endpoint not found" } ``` Circuit breaker open (503): ```json { "error": "Circuit breaker is open. Endpoint is paused due to high error rate." } ``` Rate limit exceeded (429): ```json { "error": "Monthly request limit reached. Upgrade plan for more." } ``` Payload too large (413): ```json { "error": "Payload too large. Maximum size is 512 KB." } ``` --- ## Management API ### Authentication **API Keys (Recommended):** All management endpoints accept Bearer token authentication: ``` Authorization: Bearer cr_live_xxx... ``` **Session Auth (Alternative):** Dashboard routes also accept NextAuth session cookies for browser-based access. **Creating API Keys:** 1. Sign in to dashboard at https://coffeerelay.dev/dashboard/settings 2. Scroll to "API Keys" section 3. Click "Create Key" 4. Give it a name and copy the key (shown only once) **Plan Limits:** - Free: 1 API key - Starter: 3 API keys - Pro: 10 API keys ### Agent Bootstrap (One-Shot Setup) **Endpoint:** `POST /api/agent/bootstrap` **Description:** Creates endpoint + destination in one API call. Perfect for CI/CD and agent workflows. **Authentication:** Bearer token (required) **Request:** ```json { "destinationUrl": "https://your-app.com/webhook", "name": "My Agent Endpoint" // optional } ``` **Response (201):** ```json { "ingestUrl": "https://coffeerelay.dev/api/in/abc123", "endpoint": { "id": "clx...", "name": "My Agent Endpoint", "slug": "abc123", "signingSecret": "whsec_...", "isActive": true, "createdAt": "2026-07-19T00:00:00.000Z" }, "destination": { "id": "clx...", "url": "https://your-app.com/webhook", "isActive": true, "createdAt": "2026-07-19T00:00:00.000Z" } } ``` **Example:** ```bash curl -X POST https://coffeerelay.dev/api/agent/bootstrap \ -H "Authorization: Bearer cr_live_xxx..." \ -H "Content-Type: application/json" \ -d '{"destinationUrl": "https://my-agent.com/webhook"}' ``` ### Management Endpoints All endpoints below support Bearer token authentication. ``` GET /api/endpoints List endpoints POST /api/endpoints Create endpoint GET /api/endpoints/{id} Get endpoint details DELETE /api/endpoints/{id} Delete endpoint GET /api/endpoints/{id}/destinations List destinations POST /api/endpoints/{id}/destinations Add destination DELETE /api/endpoints/{id}/destinations/{did} Remove destination GET /api/logs/{endpointId} Fetch logs (paginated) POST /api/replay/{logId} Replay webhook GET /api/dlq?endpointId={id} List DLQ items POST /api/dlq Replay/discard DLQ items GET /api/endpoints/{id}/health Endpoint health stats POST /api/endpoints/{id}/reset-circuit Reset circuit breaker GET /api/sessions?endpointId={id} List sessions POST /api/sessions Create/update session GET /api/sessions/{sessionId} Get session state DELETE /api/sessions/{sessionId} Delete session ``` --- ## Signature Verification Every forwarded webhook includes: ``` x-coffeerelay-signature: t=1234567890,v1=abc123def456... ``` **Format:** `t={unix_timestamp},v1={hmac_sha256_hex}` **Algorithm:** 1. Extract timestamp (`t`) and signature (`v1`) 2. Construct payload: `{timestamp}.{raw_body}` 3. Compute HMAC-SHA256 of payload using your signing secret 4. Compare with `v1` using constant-time comparison **Implementation (Node.js):** ```javascript import { createHmac, timingSafeEqual } from "crypto"; function verifySignature(header, rawBody, secret) { if (!header || !rawBody || !secret) return false; const parts = header.split(","); if (parts.length !== 2) return false; const timestamp = parts[0].replace("t=", ""); const signature = parts[1].replace("v1=", ""); const payload = `${timestamp}.${rawBody}`; const expected = createHmac("sha256", secret) .update(payload) .digest("hex"); if (signature.length !== expected.length) return false; return timingSafeEqual( Buffer.from(signature), Buffer.from(expected) ); } // Express middleware app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => { const signature = req.headers["x-coffeerelay-signature"]; const rawBody = req.body.toString(); const secret = process.env.COFFEERELAY_SECRET; if (!verifySignature(signature, rawBody, secret)) { return res.status(401).json({ error: "Invalid signature" }); } const payload = JSON.parse(rawBody); // Process webhook... res.json({ received: true }); }); ``` **Implementation (Python/FastAPI):** ```python import hmac import hashlib from fastapi import Request, HTTPException def verify_signature(header: str, raw_body: bytes, secret: str) -> bool: if not header or not raw_body or not secret: return False parts = header.split(",") if len(parts) != 2: return False timestamp = parts[0].replace("t=", "") signature = parts[1].replace("v1=", "") payload = f"{timestamp}.{raw_body.decode()}" expected = hmac.new( secret.encode(), payload.encode(), hashlib.sha256 ).hexdigest() return hmac.compare_digest(signature, expected) @app.post("/webhook") async def webhook(request: Request): raw_body = await request.body() signature = request.headers.get("x-coffeerelay-signature") secret = os.getenv("COFFEERELAY_SECRET") if not verify_signature(signature, raw_body, secret): raise HTTPException(status_code=401, detail="Invalid signature") payload = await request.json() # Process webhook... return {"received": True} ``` --- ## Claude Code Integration CoffeeRelay supports two modes for Claude Code HTTP hooks: ### 1. Events Only (Logging/Observability) **Endpoint:** `POST /api/in/{slug}` **Events to capture:** - `PostToolUse`: After each tool execution - `Stop`: When session ends - `Notification`: Agent notifications - `SessionStart/End`: Session lifecycle - `SubagentStart/Stop`: Subagent spawning **Use cases:** - Team activity feeds - Audit logging for compliance - Analytics on tool usage - Multi-destination alerts ### 2. Policy Enforcement (Safety) **Endpoint:** `POST /api/hooks/evaluate/{slug}` **Event:** `PreToolUse` only **Flow:** 1. Claude Code sends tool call details before execution 2. CoffeeRelay evaluates against policy rules 3. Returns `permissionDecision: "deny"` with reason, or allows (empty 200) **Example policy rules:** - Block `rm -rf` and `sudo rm` commands - Prevent writes to `.env` files - Block network calls (`curl`, `wget`) - Read-only mode (deny all Edit/Write tools) **Configuration:** Dashboard → Endpoint → Advanced → Policy Rules --- ## Plans and Pricing | Feature | Free | Starter ($10/mo) | Pro ($25/mo) | |---------|------|------------------|--------------| | **Endpoints** | 1 | 5 | 25 | | **Destinations per endpoint** | 1 | 3 | 10 | | **Requests per month** | 1,000 | 10,000 | 100,000 | | **Log retention** | 1 day | 7 days | 30 days | | **Replay failed deliveries** | No | Yes | Yes | | **Email alerts** | No | Yes | Yes | | **Circuit breaker** | No | Yes | Yes | | **Idempotency keys** | No | Yes | Yes | | **Dead Letter Queue** | No | No | Yes | | **Policy rules per endpoint** | 0 | 5 | Unlimited | | **Agent sessions per endpoint** | 0 | 50 | 500 | | **Session TTL** | N/A | 24h | 72h | | **AI agents** | 1 | 3 | 10 | | **Alert channels** | 0 | 1 | 5 | **Billing:** Monthly via Stripe. Cancel anytime. No contracts. --- ## For AI Agents — Setup Guide ### Current Reality (Pre-API-Keys) **What works today:** ✅ **Public ingest endpoint** — POST to `/api/in/{slug}` (no auth) ✅ **Signature verification** — Verify `x-coffeerelay-signature` header ✅ **Session state** — Webhooks with `x-session-id` get state hydrated ✅ **Idempotency** — Send `idempotency-key` header for deduplication ✅ **Real-time fan-out** — One webhook → multiple destinations instantly **What requires dashboard today:** ⚠️ **Endpoint/destination management** — Currently manual via GitHub OAuth dashboard ⚠️ **Session CRUD** — Create/update sessions via dashboard Sessions tab ⚠️ **Policy rules** — Configure via dashboard **Coming in Phase 1:** 🔜 **API keys** — Bearer token auth for all management endpoints 🔜 **Agent bootstrap endpoint** — One-shot setup: API call → ingest URL 🔜 **OpenAPI spec** — Machine-readable contract at `/openapi.json` 🔜 **TypeScript SDK** — `npm i @coffeerelay/sdk` 🔜 **Official MCP server** — First-party MCP tools for Cursor/Claude ### Setup Path Today (Manual) **Step 1: Human creates account and endpoint** 1. Sign up at https://coffeerelay.dev/sign-in (GitHub OAuth) 2. Dashboard → Endpoints → New Endpoint 3. Copy slug and signing secret **Step 2: Human adds destination(s)** 1. Endpoint detail page → Destinations tab 2. Add destination URL where agent receives webhooks 3. Copy ingest URL: `https://coffeerelay.dev/api/in/{slug}` **Step 3: Agent verifies signatures** When agent receives forwarded webhooks: ```javascript // Node.js const signature = req.headers["x-coffeerelay-signature"]; const rawBody = req.body.toString(); // Must be raw, not parsed const secret = process.env.COFFEERELAY_SECRET; if (!verifySignature(signature, rawBody, secret)) { return res.status(401).json({ error: "Invalid signature" }); } ``` **Step 4: Use session state (optional)** For stateful workflows: 1. Human creates session via dashboard Sessions tab 2. External service sends webhook with matching `session_id` 3. Agent receives webhook with state in headers: - `x-session-state`: Full state as JSON - `x-session-metadata`: Metadata - `x-correlation-id`: Trace ID **Step 5: Monitor and replay** Human uses dashboard to: - View delivery logs - Replay failed webhooks - Monitor circuit breaker status - Check DLQ (Pro) ### Recommended Setup Order (Once API Keys Ship) ``` 1. Create API key at /dashboard/settings 2. Call POST /api/endpoints → get ingest URL 3. Call POST /api/endpoints/{id}/destinations → add agent webhook URL 4. Register ingest URL with upstream (Stripe, GitHub, etc.) 5. POST test event; verify via GET /api/logs/{endpointId} 6. Verify signature in agent code 7. Create sessions via API if needed ``` ### Common Failure Modes & Fixes **Problem:** Signature verification fails **Causes:** - Using parsed body instead of raw bytes - Wrong signing secret - Timestamp tolerance issue (clock skew) **Fix:** Use raw body middleware, verify secret, check timestamp **Problem:** Duplicate webhooks **Fix:** Send `idempotency-key` header (Starter/Pro) **Problem:** Agent missed webhooks during deploy **Fix:** Use DLQ replay (Pro) or manual replay via dashboard **Problem:** Agent retry loop on error **Fix:** Enable circuit breaker (Starter/Pro) --- ## Why Event-Driven Over Polling for AI Agents **Saves tokens and compute:** - Agents only activate when real events arrive - No empty polling requests burning API quota **Near real-time reactions:** - Respond instantly to payments, code pushes, support tickets - No 5-minute cron delay **Simpler architecture:** - No cron jobs, timers, or polling loops to manage - No "check for changes" API calls **Lower API costs:** - Most APIs charge per request - Webhooks = 1 request per event vs polling = N requests per event --- ## Links - **Homepage**: https://coffeerelay.dev - **Sign Up**: https://coffeerelay.dev/sign-in (GitHub OAuth) - **Dashboard**: https://coffeerelay.dev/dashboard - **Quick Reference**: https://coffeerelay.dev/llms.txt - **Full Docs (this file)**: https://coffeerelay.dev/llms-full.txt --- **Last Updated:** 2026-07-18 **Version:** 1.1 (post-sessions, pre-API-keys)