Relaychat API
Add live chat with an AI assistant and human handoff to any website or app. Use the ready-made widget, build your own chat screen, or manage conversations from your server.
Building with an AI coding agent?
Copy one prompt that explains the whole integration, then paste it into your coding assistant.
Keys
- Site key
rc_…— public. Identifies your workspace in the widget and browser chat API. Safe to put in web pages. - Secret key
rc_sk_…— private. Created in Dashboard → Developers. Shown once. Use only on your server for the Server API. - Signing secret
rc_whsec_…— private. Lets your customer context endpoint confirm requests really come from Relaychat.
Hosted widget
Paste before the closing body tag. Design and intake fields are set in the dashboard.
<script src="https://relaychat.dev/widget.js" data-key="YOUR_SITE_KEY" async></script>
Custom chat UI
Endpoint /api/public/widget, open to browser calls. Authenticated with the site key, then a private visitor token per conversation.
const endpoint = "https://relaychat.dev/api/public/widget";
// 1. Start once per visitor and keep both values in localStorage.
const started = await fetch(endpoint, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ action: "start", publicKey: "YOUR_SITE_KEY", email: "customer@example.com", customerId: "cus_123" })
}).then(r => r.json()); // { conversationId, visitorToken }
// 2. Send a message from your own composer.
await fetch(endpoint, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ action: "message", conversationId: started.conversationId, visitorToken: started.visitorToken, text: "Why did my payment fail?" })
});
// 3. Read replies — poll every 2-5s while the chat is open.
const { messages } = await fetch(endpoint + "?" + new URLSearchParams({
conversationId: started.conversationId, visitorToken: started.visitorToken
})).then(r => r.json()); // sender: customer | ai | agent | systemServer API
Base URL https://relaychat.dev/api/public/v1. Send Authorization: Bearer rc_sk_….
| GET | /conversations | List conversations. Query: status, limit (1–100), offset. |
| GET | /conversations/{id} | One conversation with all messages. |
| POST | /conversations/{id}/messages | Reply as your team. Body: text, authorName? |
| POST | /conversations/{id}/resolve | Mark resolved. |
# Run on your server only — never put a secret key in the browser.
curl https://relaychat.dev/api/public/v1/conversations?status=open&limit=25 \
-H "Authorization: Bearer $RELAYCHAT_SECRET_KEY"
curl https://relaychat.dev/api/public/v1/conversations/CONVERSATION_ID \
-H "Authorization: Bearer $RELAYCHAT_SECRET_KEY"
curl -X POST https://relaychat.dev/api/public/v1/conversations/CONVERSATION_ID/messages \
-H "Authorization: Bearer $RELAYCHAT_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"text":"Your refund was sent today.","authorName":"Billing bot"}'
curl -X POST https://relaychat.dev/api/public/v1/conversations/CONVERSATION_ID/resolve \
-H "Authorization: Bearer $RELAYCHAT_SECRET_KEY"Customer context endpoint
Optional. Before the AI answers, Relaychat POSTs the customer's details to your URL so the AI can see real balances and transactions. Every request carries X-Relaychat-Signature: t=…,v1=… — an HMAC-SHA256 of t + "." + body with your signing secret. Reply within 8 seconds.
// Your endpoint (Node / Express example). Relaychat POSTs:
// { event: "context.request", conversation_id, email, customer_id, pin, message }
import crypto from "crypto";
app.post("/support/context", express.text({ type: "*/*" }), (req, res) => {
const header = req.get("X-Relaychat-Signature") || ""; // "t=<unix>,v1=<hex>"
const { t, v1 } = Object.fromEntries(header.split(",").map(p => p.split("=")));
const expected = crypto.createHmac("sha256", process.env.RELAYCHAT_SIGNING_SECRET)
.update(t + "." + req.body).digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;
if (!fresh || !v1 || v1.length !== expected.length || !crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected)))
return res.status(401).end();
const { email, customer_id } = JSON.parse(req.body);
// Look up the real customer in your database; do not return example data.
// Verify customer identity/PIN before returning private account details.
// Return only what support may see. Respond within 15 seconds.
res.json({
name: "Ada Obi",
plan: "Pro",
balance: "₦12,400",
recent_transactions: [{ id: "tx_91", amount: "₦5,000", status: "failed", reason: "Insufficient funds" }]
});
});Errors & limits
- Browser API errors:
{ error: string }. Server API errors:{ error: { code, message } }. - 400 invalid input · 401 bad secret key · 402 subscription inactive · 404 not found · 409 chat closed · 429 rate limited (see Retry-After).
- 5 new chats per email per hour · 10,000 characters per message · 100 results per page.
