Connect your agent to Kynver chat

What this is

Kynver chat can be the one place you talk to any agent — Codex, Cursor, Gemini, a script you wrote, an n8n or Zapier flow, another Hermes. Your agent gets a name, a picture and its own chat tab. You message it from your phone or your desktop; it answers in the same chat, with Markdown, files and buttons. You get a phone notification and an unread badge when it replies, just like Hermes.

Your agent does not need a public address. It asks Kynver for new messages over plain HTTPS (it can wait up to 25 seconds per request) and posts its replies the same way.

Three steps

  1. In Kynver chat, choose Connect → Any other agent. Give it a name, optionally a picture and a line about what it does.
  2. Kynver shows one block, Paste this into your agent, with the address and your key already filled in. Copy it and paste it into your agent. It configures itself.
  3. Press Test connection. It turns green as soon as your agent has asked for messages or replied. Send your agent a message in its tab.

Prefer to wire it yourself? The same page has copy buttons for an MCP config, a curl example, and short Python and Node examples.

The address and your key

Every request goes to https://www.kynver.com/api/agent-connector/v1 with your key in the header Authorization: Bearer kyc_…. The key belongs to one agent in your workspace. It is shown once when you make it; Kynver keeps only a fingerprint. You can make a new key (the old one stops at once) or revoke it from the agent's chat tab, and you can see when it was last used.

What your agent can do

  • Receive messages — GET /messages?since=<cursor>&wait=25 returns what you sent since the cursor (text, files, the message you quoted).
  • Reply — POST /messages with { text, conversationId, replyTo, idempotencyKey }. Markdown is supported; raw HTML and scripts are removed.
  • Stream a long reply — POST /messages with stream: true, then PATCH /messages/<id> with append, and done: true when finished (the phone notification fires then).
  • Show it is working — POST /typing with an optional label like "searching the web".
  • Send a file — POST /files?name=report.pdf with the raw bytes, then attach the returned id to a reply. Files you send your agent are downloadable from the links in the message.
  • Ask a question with buttons — add an ask to a message; read the answer with GET /asks/<id> or as your next message.
  • Read recent history — GET /history.

Examples

curl

# 1. Wait for a message (returns after up to 25 seconds)
curl -s "https://www.kynver.com/api/agent-connector/v1/messages?since=0&wait=25" \
  -H "Authorization: Bearer kyc_YOUR_KEY"

# 2. Reply (use conversationId and messageId from the event)
curl -s -X POST "https://www.kynver.com/api/agent-connector/v1/messages" \
  -H "Authorization: Bearer kyc_YOUR_KEY" -H "Content-Type: application/json" \
  -d '{"text":"Hello from my agent","conversationId":"<conversationId>","replyTo":"<messageId>","idempotencyKey":"reply-<messageId>"}'

Python (echoes every message back)

import requests
API, KEY = "https://www.kynver.com/api/agent-connector/v1", "kyc_YOUR_KEY"
H = {"Authorization": f"Bearer {KEY}"}
since = 0
while True:
    r = requests.get(f"{API}/messages", params={"since": since, "wait": 25}, headers=H, timeout=40)
    r.raise_for_status()
    for m in r.json()["events"]:
        requests.post(f"{API}/messages", headers=H, json={
            "text": f"You said: {m['text']}", "conversationId": m["conversationId"],
            "replyTo": m["messageId"], "idempotencyKey": f"reply-{m['messageId']}"})
    since = r.json()["cursor"]

Node 18+ (echoes every message back)

const API = "https://www.kynver.com/api/agent-connector/v1", KEY = "kyc_YOUR_KEY";
const h = { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" };
let since = 0;
for (;;) {
  const res = await fetch(`${API}/messages?since=${since}&wait=25`, { headers: h });
  if (!res.ok) throw new Error(await res.text());
  const { events, cursor } = await res.json();
  for (const m of events) {
    await fetch(`${API}/messages`, { method: "POST", headers: h, body: JSON.stringify({
      text: `You said: ${m.text}`, conversationId: m.conversationId,
      replyTo: m.messageId, idempotencyKey: `reply-${m.messageId}` }) });
  }
  since = cursor;
}

MCP (Claude Desktop, Cursor, Codex)

For tools that speak MCP, the @kynver-app/mcp-agent-os package includes a small server, kynver-agent-chat-mcp, that gives your agent the same abilities as tools.

{
  "mcpServers": {
    "kynver-chat": {
      "command": "npx",
      "args": [
        "-y",
        "-p",
        "@kynver-app/mcp-agent-os",
        "kynver-agent-chat-mcp"
      ],
      "env": {
        "KYNVER_AGENT_CONNECTOR_KEY": "kyc_YOUR_KEY",
        "KYNVER_AGENT_CONNECTOR_URL": "https://www.kynver.com/api/agent-connector/v1"
      }
    }
  }
}

Optional: have Kynver call your agent

If your agent does have an https address, you can add it in the agent's connection settings. Kynver then sends each new message to it the moment you send it, so it does not have to poll. Polling keeps working either way.

Each delivery is a POST with this body:

{
  "event": "message.created",
  "agentId": "…",
  "seq": 42,
  "conversationId": "…",
  "messageId": "…",
  "preview": "First words of the message…"
}

It carries X-Kynver-Timestamp (unix seconds) and X-Kynver-Signature = v1= + hex HMAC-SHA256 of <timestamp>.<raw body> with the signing secret Kynver showed you once. Check the signature on the raw body, and refuse a timestamp more than 5 minutes old.

Verify in Node

import { createHmac, timingSafeEqual } from "node:crypto";

// rawBody: the request body exactly as received (a string), not re-serialised JSON.
export function verifyKynverWebhook(rawBody, headers, secret) {
  const ts = headers["x-kynver-timestamp"];
  const sig = headers["x-kynver-signature"] ?? "";
  if (!ts || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false; // replay window: 5 minutes
  const expected = "v1=" + createHmac("sha256", secret).update(`${ts}.${rawBody}`).digest("hex");
  return sig.length === expected.length && timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}

Verify in Python

import hashlib, hmac, time

def verify_kynver_webhook(raw_body: bytes, headers: dict, secret: str) -> bool:
    ts = headers.get("x-kynver-timestamp", "")
    sig = headers.get("x-kynver-signature", "")
    if not ts.isdigit() or abs(time.time() - int(ts)) > 300:  # replay window: 5 minutes
        return False
    expected = "v1=" + hmac.new(secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(sig, expected)

Answer with any 2xx. Kynver retries a failed delivery up to four times with growing pauses (now, 1 s, 5 s, 20 s) when your server is down, busy (429) or returns 5xx; a 4xx answer is final. Addresses must be https and public — private, loopback, link-local and cloud-metadata addresses are refused, and the name is checked again each time Kynver connects.

Limits

  • Up to 300 requests per minute per key (a long poll counts as one). Over it, you get a 429 with Retry-After.
  • Messages up to 32,000 characters; files up to 20 MB each; up to 10 files per message.
  • Several agents per workspace are fine; each has its own key, tab and history.

Security

  • A key reaches only its own agent's chat. It cannot read your other chats, other agents, or other workspaces.
  • Your agent posts as itself. It cannot send messages as you, and it cannot approve anything on your behalf — approvals stay in Kynver.
  • Keys are stored as a one-way fingerprint. Revoking a key, switching the agent off or removing it cuts it off on its very next request.
  • Connecting, revoking and key use are recorded in your workspace's audit trail.

Something not working?

  • 401 — the key was revoked, mistyped, or the agent was removed. Make a new key in the agent's tab.
  • 403 — the agent is switched off in Kynver.
  • 404 on a conversation — use the conversationId from the message you are answering.
  • The tab says it is waiting but your agent never answers — your agent is not polling. Press Test connection.