This guide covers using the Bondi SDK without the NestJS adapter — for Express apps, Fastify apps, simple scripts, or any Node.js runtime.
npm install @bondi-labs/integration-sdk zod
npx bondi init
Same flow as NestJS — writes BONDI_API_URL, BONDI_WORKSPACE_ID, and BONDI_INTEGRATION_TOKEN to .env.
Plain TypeScript module — no decorators needed.
// src/bondi.integration.ts
import { defineIntegration, defineAction, defineTrigger } from "@bondi-labs/integration-sdk/core";
import { z } from "zod";
export const contactCreatedPayload = z.object({
id: z.string(),
email: z.string().email(),
});
// Export actions individually so framework adapters (`actionHandler`) can
// infer their `body` and `response` types from the Zod schemas.
export const createContact = defineAction({
name: "createContact",
label: "Create Contact",
method: "POST",
endpoint: "/contacts",
body: z.object({ email: z.string(), fullName: z.string() }),
response: z.object({ id: z.string() }),
});
export default defineIntegration({
name: "My CRM",
slug: "my-crm",
category: "crm",
baseUrl: "https://api.mycrm.com/v1",
services: [
{
name: "contacts",
label: "Contacts",
triggers: [
defineTrigger({ name: "contact.created", label: "Contact Created", payload: contactCreatedPayload }),
],
actions: [createContact],
},
],
});
npx bondi sync
// src/emit-example.ts
import { createBondiClient } from "@bondi-labs/integration-sdk/core";
import myCRM from "./bondi.integration";
const client = createBondiClient({
workspaceId: process.env.BONDI_WORKSPACE_ID,
token: process.env.BONDI_INTEGRATION_TOKEN,
apiUrl: process.env.BONDI_API_URL,
// optional:
onEmitError: (err, event) => console.error(`emit ${event} failed:`, err),
});
const bondi = client.bind(myCRM);
bondi.triggers["contact.created"].emit({ id: "1", email: "alice@example.com" });
Bondi calls your action endpoints with HMAC-signed requests. The framework adapters do everything for you: HMAC signature verification, raw-body capture, Zod body validation, response validation, and JSON serialization.
actionHandler infers the body and response types from the action's Zod
schemas — no as any, no JSON.parse, no verifyRequest boilerplate.
// src/server.ts
import express from "express";
import { actionHandler } from "@bondi-labs/integration-sdk/express";
import { createContact } from "./bondi.integration";
const app = express();
app.post("/contacts", actionHandler(createContact, async ({ body }) => {
// body: { email: string; fullName: string } — typed via Zod
// signature verified, action header validated, body parsed
return { id: "new-id" };
}));
app.listen(3000);
The token is read from BONDI_INTEGRATION_TOKEN automatically. Pass it
explicitly if you prefer: actionHandler(createContact, fn, { token }).
import Fastify from "fastify";
import { actionHandler } from "@bondi-labs/integration-sdk/fastify";
import { createContact } from "./bondi.integration";
const app = Fastify();
// One-time setup so Fastify hands us raw bytes (HMAC needs the original body):
app.addContentTypeParser("application/json", { parseAs: "buffer" },
(_req, body, done) => done(null, body));
app.post("/contacts", actionHandler(createContact, async ({ body }) => {
return { id: "new-id" };
}));
Anywhere with the standard Web Request/Response:
// app/api/contacts/route.ts (Next.js App Router)
import { withAction } from "@bondi-labs/integration-sdk/web";
import { createContact } from "@/bondi.integration";
export const POST = withAction(createContact, async ({ body }) => {
return { id: "new-id" };
});
If you don't have an action definition handy (or want full control over the response shape), use the verification primitives:
// Express — middleware that just verifies the signature
import { bondiExpressMiddleware } from "@bondi-labs/integration-sdk/express";
const verify = bondiExpressMiddleware();
app.post("/custom", verify, (req, res) => { /* req.body parsed, sig verified */ });
// Web standard — bare verifier
import { verifyWebRequest } from "@bondi-labs/integration-sdk/core";
const result = await verifyWebRequest(request);
if (!result.valid) return new Response(null, { status: 401 });
const data = JSON.parse(result.body!);
// Anywhere — pure function, no framework dependency
import { verifyRequest } from "@bondi-labs/integration-sdk/core";
const result = verifyRequest({
token: process.env.BONDI_INTEGRATION_TOKEN!,
signature: headers["x-bondi-signature"],
timestamp: headers["x-bondi-timestamp"],
action: headers["x-bondi-action"],
rawBody, // string of the original bytes — never re-serialized JSON
});