Using DenchClaw with Node.js
Using DenchClaw with Node.js
DenchClaw stores all your CRM data in a local DuckDB database. That means your Node.js scripts can read and write directly to it — no HTTP round trips, no API rate limits, no cloud dependency. You can also use DenchClaw's REST API when you want to go through the agent layer. This guide covers both patterns and shows you how to wire them together into real automation.
Prerequisites#
- DenchClaw installed and running (
npx denchclaw) - Node.js 18+
- The DenchClaw gateway running (
openclaw gateway start)
Pattern 1: Direct DuckDB Access from Node.js#
The fastest way to read CRM data from Node is direct DuckDB access. Install the official Node.js DuckDB binding:
npm install duckdbThen connect to your workspace database:
const duckdb = require('duckdb');
const path = require('path');
const os = require('os');
const DB_PATH = path.join(
os.homedir(),
'.openclaw-dench/workspace/workspace.duckdb'
);
const db = new duckdb.Database(DB_PATH, { access_mode: 'READ_ONLY' });
const conn = db.connect();
conn.all(
`SELECT ef.value AS name, ef2.value AS email
FROM objects o
JOIN entry_fields ef ON ef.entry_id = o.id AND ef.field_key = 'name'
JOIN entry_fields ef2 ON ef2.entry_id = o.id AND ef2.field_key = 'email'
WHERE o.object_type = 'person'
LIMIT 100`,
(err, rows) => {
if (err) throw err;
console.log(rows);
db.close();
}
);Use READ_ONLY mode when DenchClaw is running so you don't conflict with its writes. For write operations, use the REST API (see below) or stop the gateway first.
Querying with PIVOT Views#
DenchClaw creates flattened PIVOT views for each object type. If you have a company object, there's a v_company view you can query directly:
conn.all(
`SELECT name, domain, status, created_at
FROM v_company
WHERE status = 'customer'
ORDER BY created_at DESC`,
(err, rows) => {
if (err) throw err;
rows.forEach(row => console.log(row.name, row.domain));
}
);These views are much easier to work with than the raw EAV tables.
Pattern 2: DenchClaw REST API from Node.js#
The gateway exposes a REST API at http://localhost:3000. Use it when you want to trigger agent actions, not just read data:
const fetch = require('node-fetch'); // npm install node-fetch@2
const BASE = 'http://localhost:3000/api';
const TOKEN = process.env.DENCHCLAW_TOKEN; // set in your .env
async function getLeads() {
const res = await fetch(`${BASE}/entries?objectType=person&status=lead`, {
headers: { Authorization: `Bearer ${TOKEN}` }
});
return res.json();
}
async function updateLead(id, fields) {
const res = await fetch(`${BASE}/entries/${id}`, {
method: 'PATCH',
headers: {
Authorization: `Bearer ${TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ fields })
});
return res.json();
}
// Example: mark a lead as contacted
updateLead('abc123', { status: 'contacted', last_contact: new Date().toISOString() })
.then(r => console.log('Updated:', r));Find your API token in ~/.openclaw-dench/workspace/config.json or generate one via openclaw auth token.
Building a Lead Enrichment Script#
Here's a complete enrichment script that reads leads from DuckDB, calls Clearbit (or any enrichment API you have access to), and writes the results back via the REST API:
const duckdb = require('duckdb');
const fetch = require('node-fetch');
const os = require('os');
const path = require('path');
const DB_PATH = path.join(os.homedir(), '.openclaw-dench/workspace/workspace.duckdb');
const BASE_URL = 'http://localhost:3000/api';
const TOKEN = process.env.DENCHCLAW_TOKEN;
async function enrichLead(email) {
// Replace with your enrichment API of choice
const res = await fetch(`https://person.clearbit.com/v2/people/find?email=${email}`, {
headers: { Authorization: `Bearer ${process.env.CLEARBIT_KEY}` }
});
if (!res.ok) return null;
return res.json();
}
async function updateEntry(id, fields) {
await fetch(`${BASE_URL}/entries/${id}`, {
method: 'PATCH',
headers: {
Authorization: `Bearer ${TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ fields })
});
}
async function main() {
const db = new duckdb.Database(DB_PATH, { access_mode: 'READ_ONLY' });
const conn = db.connect();
conn.all(
`SELECT o.id, ef.value AS email
FROM objects o
JOIN entry_fields ef ON ef.entry_id = o.id AND ef.field_key = 'email'
LEFT JOIN entry_fields ef2 ON ef2.entry_id = o.id AND ef2.field_key = 'linkedin_url'
WHERE o.object_type = 'person'
AND ef2.value IS NULL
LIMIT 50`,
async (err, rows) => {
if (err) throw err;
db.close();
for (const row of rows) {
console.log(`Enriching ${row.email}...`);
const data = await enrichLead(row.email);
if (!data) continue;
await updateEntry(row.id, {
linkedin_url: data.linkedin?.handle,
company: data.employment?.name,
title: data.employment?.title,
location: data.location
});
// Respect rate limits
await new Promise(r => setTimeout(r, 300));
}
console.log('Done.');
}
);
}
main().catch(console.error);Run it with:
DENCHCLAW_TOKEN=your_token CLEARBIT_KEY=your_key node enrich-leads.jsWebhook Triggers: Node Functions Listening for Events#
DenchClaw can send webhooks when entries are created or updated. Set up a webhook listener in Node to react to CRM events in real time.
First, configure the webhook in DenchClaw (via your agent or the config file):
Tell DenchClaw agent: "Send a webhook to http://localhost:4000/webhook whenever a new lead is created"
Then create your webhook server:
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json());
const WEBHOOK_SECRET = process.env.DENCHCLAW_WEBHOOK_SECRET;
function verifySignature(req) {
const sig = req.headers['x-denchclaw-signature'];
const expected = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(JSON.stringify(req.body))
.digest('hex');
return sig === expected;
}
app.post('/webhook', async (req, res) => {
if (!verifySignature(req)) {
return res.status(401).json({ error: 'Invalid signature' });
}
const { event, entry } = req.body;
if (event === 'entry.created' && entry.objectType === 'person') {
console.log('New lead:', entry.fields.name, entry.fields.email);
await notifySlack(entry);
}
res.json({ ok: true });
});
app.listen(4000, () => console.log('Webhook server on :4000'));Sending Slack Notifications for New Leads#
Wire the webhook to Slack using their Incoming Webhooks:
async function notifySlack(entry) {
const { name, email, company } = entry.fields;
const message = {
text: `🚀 New lead in DenchClaw!`,
blocks: [
{
type: 'section',
text: {
type: 'mrkdwn',
text: `*New Lead Added*\n*Name:* ${name || 'Unknown'}\n*Email:* ${email || 'N/A'}\n*Company:* ${company || 'N/A'}`
}
},
{
type: 'actions',
elements: [
{
type: 'button',
text: { type: 'plain_text', text: 'View in DenchClaw' },
url: `http://localhost:3000/entries/${entry.id}`
}
]
}
]
};
await fetch(process.env.SLACK_WEBHOOK_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(message)
});
}Building a Custom Sync Worker#
Need to sync DenchClaw with another system — say, your billing tool or a spreadsheet? Here's a sync worker pattern:
const duckdb = require('duckdb');
const fetch = require('node-fetch');
class DenchClawSync {
constructor(options = {}) {
this.dbPath = options.dbPath || `${process.env.HOME}/.openclaw-dench/workspace/workspace.duckdb`;
this.apiBase = options.apiBase || 'http://localhost:3000/api';
this.token = options.token || process.env.DENCHCLAW_TOKEN;
}
query(sql) {
return new Promise((resolve, reject) => {
const db = new duckdb.Database(this.dbPath, { access_mode: 'READ_ONLY' });
db.all(sql, (err, rows) => {
db.close();
if (err) reject(err);
else resolve(rows);
});
});
}
async upsertEntry(objectType, fields, matchField = 'email') {
// Check if entry exists
const existing = await this.query(
`SELECT o.id FROM objects o
JOIN entry_fields ef ON ef.entry_id = o.id
WHERE o.object_type = '${objectType}'
AND ef.field_key = '${matchField}'
AND ef.value = '${fields[matchField]}'
LIMIT 1`
);
if (existing.length > 0) {
// Update
const res = await fetch(`${this.apiBase}/entries/${existing[0].id}`, {
method: 'PATCH',
headers: {
Authorization: `Bearer ${this.token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ fields })
});
return { action: 'updated', id: existing[0].id, ...(await res.json()) };
} else {
// Create
const res = await fetch(`${this.apiBase}/entries`, {
method: 'POST',
headers: {
Authorization: `Bearer ${this.token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ objectType, fields })
});
return { action: 'created', ...(await res.json()) };
}
}
}
// Usage: sync customers from Stripe
async function syncStripeCustomers() {
const sync = new DenchClawSync();
const stripe = require('stripe')(process.env.STRIPE_KEY);
const customers = await stripe.customers.list({ limit: 100 });
for (const customer of customers.data) {
const result = await sync.upsertEntry('person', {
name: customer.name,
email: customer.email,
stripe_id: customer.id,
status: 'customer',
mrr: (customer.metadata.mrr || 0)
});
console.log(`${result.action}: ${customer.email}`);
}
}
syncStripeCustomers().catch(console.error);Scheduling with node-cron#
Run your sync scripts on a schedule:
npm install node-cronconst cron = require('node-cron');
// Sync every night at 2 AM
cron.schedule('0 2 * * *', async () => {
console.log('[cron] Starting nightly sync...');
try {
await syncStripeCustomers();
console.log('[cron] Sync complete');
} catch (err) {
console.error('[cron] Sync failed:', err);
}
});
// Run lead enrichment every hour
cron.schedule('0 * * * *', async () => {
console.log('[cron] Running lead enrichment...');
// call your enrichment function
});
console.log('DenchClaw sync worker running. Press Ctrl+C to stop.');Keep this running with PM2:
npm install -g pm2
pm2 start sync-worker.js --name denchclaw-sync
pm2 save
pm2 startup # auto-start on rebootPutting It All Together#
The combination of direct DuckDB access (for reads) and the REST API (for writes + agent triggers) gives you a powerful automation foundation. Direct reads are fast and don't require the gateway to be running. REST writes go through DenchClaw's agent layer, which means they trigger webhooks, update views, and keep everything consistent.
Common patterns:
- Nightly sync: Pull customers from billing → upsert into DenchClaw
- Lead enrichment: Watch for new people entries → enrich via API → update fields
- Slack alerts: Webhook listener → format and post to channel
- Pipeline reports: Query DuckDB directly → generate CSV or send email
The full source for these examples is in the DenchClaw GitHub repo under /examples/node.
Ready to try DenchClaw? Install in one command: npx denchclaw. Full setup guide →
