HTTP endpoint (any database)

ByoTalk replicates natively into PostgreSQL, MySQL/MariaDB and MongoDB. For anything else (SQL Server, Oracle, DynamoDB, Firestore, Cassandra, a data lake…) point ByoTalk at an HTTPS endpoint you run, and store the data wherever you like.

Set it up

  1. Storage → Database, engine HTTP endpoint, paste your endpoint URL and save. The signing secret (whsec_…) is shown once; store it in your endpoint's configuration. You can rotate it later from the same page.
  2. Make the endpoint answer ping (below), then press Test connection.
  3. Press Enable your own database.

A complete example that stores everything in SQLite with no dependencies ships with the SDK repository: examples/http-sink/server.mjs.

The contract

Every request is POST with a JSON body and Standard Webhooks headers (webhook-id, webhook-timestamp, webhook-signature), signed with your endpoint secret. Verify with chatServer.webhooks.verify(rawBody, headers) from byotalk/server, or any Standard Webhooks library, against the raw body.

type Body Answer
ping {} 200 {"ok": true, "schemaVersion": 1}
batch {schemaVersion, batchId, users[], conversations[], members[], messages[]} any 2xx once stored
read {conversationId, seqs[]} 200 {"messages": [...]} (messages you stored)

read is used for history older than our buffer: return the messages you stored for those seq values.

Rules

  • Idempotent and version-guarded. Batches can be delivered more than once. Store a row only if it is new or its version is higher (members: updatedAt not older). Never let an older version replace a newer one.
  • Answer only after storing. A non-2xx (or timeout) makes ByoTalk keep the data and retry with backoff; the dashboard shows the sink as lagging or failing and your chat keeps working.
  • 401/403 pause the sink until you fix the endpoint and press Test connection.

Message shape

{ "id": "msg_01J9ZN...", "conversationId": "conv_01J9ZK...", "seq": 813, "senderId": "user_123",
  "text": "Can you share the invoice?", "replyTo": null, "attachments": [], "metadata": {},
  "version": 1, "createdAt": "2026-10-04T10:06:00.120Z", "editedAt": null, "deletedAt": null }

Minimal handler (Node)

import express from "express";
import { ChatServer } from "byotalk/server";

const chatServer = new ChatServer({ secretKey: process.env.BYOTALK_SECRET_KEY! });
const app = express();

app.post("/byotalk/sink", express.raw({ type: "application/json" }), async (req, res) => {
  let body: any;
  try {
    // Third argument: the endpoint's signing secret (whsec_…), not your webhook endpoints' secret.
    body = chatServer.webhooks.verify(req.body, req.headers, process.env.BYOTALK_SINK_SECRET);
  } catch {
    return res.sendStatus(401);
  }
  if (body.type === "ping") return res.json({ ok: true, schemaVersion: 1 });
  if (body.type === "batch") {
    await store(body); // upsert users, conversations, members, messages by id + version
    return res.sendStatus(204);
  }
  if (body.type === "read") return res.json({ messages: await load(body.conversationId, body.seqs) });
  res.sendStatus(400);
});