JavaScript sample
A minimal Node.js client for the BottleCRM API. Uses native fetch (Node 18+), no external dependencies.
A tiny client
const BASE = process.env.BOTTLECRM_URL.replace(/\/$/, "");
const TOKEN = process.env.BOTTLECRM_TOKEN;
async function api(path, init = {}) {
const res = await fetch(`${BASE}${path}`, {
...init,
headers: {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json",
...(init.headers ?? {}),
},
});
if (!res.ok) {
const body = await res.text();
throw new Error(`${res.status} ${res.statusText}: ${body}`);
}
return res.status === 204 ? null : res.json();
}
export async function* listLeads(filters = {}) {
const qs = new URLSearchParams(filters).toString();
let url = `/api/leads/${qs ? `?${qs}` : ""}`;
while (url) {
const page = await api(url);
yield* page.results;
url = page.next ? new URL(page.next).pathname + new URL(page.next).search : null;
}
}
export const createLead = (fields) =>
api("/api/leads/", { method: "POST", body: JSON.stringify(fields) });
export const updateLead = (id, fields) =>
api(`/api/leads/${id}/`, { method: "PATCH", body: JSON.stringify(fields) });
Example: handle an inbound webhook in Express
import crypto from "node:crypto";
import express from "express";
const app = express();
const SECRET = process.env.BOTTLECRM_WEBHOOK_SECRET;
// Capture the raw body so we can verify the signature.
app.use(express.json({
verify: (req, _res, buf) => { req.rawBody = buf; },
}));
app.post("/webhooks/bottlecrm", (req, res) => {
const sig = req.header("X-BottleCRM-Signature") ?? "";
const expected = "sha256=" + crypto
.createHmac("sha256", SECRET)
.update(req.rawBody)
.digest("hex");
if (
sig.length !== expected.length ||
!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))
) {
return res.status(401).send("Invalid signature");
}
const event = req.body;
switch (event.type) {
case "lead.converted":
// queue follow-up work, don't block the webhook
enqueueOnboardingEmail(event.data.contact.email);
break;
case "invoice.paid":
enqueueReceiptDelivery(event.data.invoice.id);
break;
}
res.status(204).end();
});
app.listen(3000);
Example: build a custom dashboard widget
import { listLeads } from "./bottlecrm-client.js";
async function thisWeekByStatus() {
const buckets = { new: 0, contacted: 0, qualified: 0, lost: 0, converted: 0 };
const since = new Date(Date.now() - 7 * 24 * 3600 * 1000).toISOString();
for await (const lead of listLeads({ created_at__gte: since })) {
buckets[lead.status] = (buckets[lead.status] ?? 0) + 1;
}
return buckets;
}
console.log(await thisWeekByStatus());
Browser usage
In the browser, never embed a JWT in client code. Instead proxy through your own server, or use a session cookie that your origin sets after BottleCRM's OAuth flow.