SDK GuideIntegrations
LangChain.js
Integrate Beltic signing with LangChain.js agents and tools
LangChain.js Integration
Build LangChain.js agents that make verifiable HTTP requests to external services using Beltic signing.
Overview
LangChain.js uses a tool-based architecture where agents invoke tools to perform actions. By creating a signed HTTP tool, your agent can make verifiable requests to any external API.
Prerequisites
- Node.js 18+
- LangChain.js installed
- Beltic CLI installed and authenticated (
beltic login) - Agent keypair generated (
beltic keygen) - Key directory hosted at an HTTPS URL
Installation
npm install langchain @langchain/core @langchain/anthropic @belticlabs/agent-signer zodCreating a Signed API Tool
Create a reusable tool that wraps HTTP requests with Beltic signing:
import { tool } from "@langchain/core/tools";
import { z } from "zod";
import { signHttpRequest, importPrivateKey } from "@belticlabs/agent-signer";
// Load credentials once
const privateKeyPem = process.env.AGENT_PRIVATE_KEY!;
const keyId = process.env.AGENT_KEY_ID!;
const keyDirectoryUrl = process.env.AGENT_KEY_DIRECTORY_URL!;
export const signedApiCall = tool(
async ({ method, url, body, headers }) => {
const privateKey = await importPrivateKey(privateKeyPem, "EdDSA");
const signedHeaders = await signHttpRequest({
method,
url,
headers: { "content-type": "application/json", ...headers },
body: body ? JSON.stringify(body) : undefined,
privateKey,
keyId,
keyDirectoryUrl,
});
const response = await fetch(url, {
method,
headers: { ...headers, ...signedHeaders },
body: body ? JSON.stringify(body) : undefined,
});
const data = await response.json();
return JSON.stringify({
status: response.status,
data,
});
},
{
name: "signed_api_call",
description:
"Make a signed HTTP request to an external API. The request will be cryptographically signed so the receiving service can verify it came from a trusted agent.",
schema: z.object({
method: z
.enum(["GET", "POST", "PUT", "DELETE"])
.describe("HTTP method"),
url: z.string().url().describe("The API endpoint URL"),
body: z
.record(z.unknown())
.optional()
.describe("Request body for POST/PUT requests"),
headers: z
.record(z.string())
.optional()
.describe("Additional HTTP headers"),
}),
}
);Using with LangChain Agents
Basic Agent Setup
import { ChatAnthropic } from "@langchain/anthropic";
import { createToolCallingAgent, AgentExecutor } from "langchain/agents";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { signedApiCall } from "./tools/signed-api-call";
// Initialize the model
const model = new ChatAnthropic({
model: "claude-sonnet-4-5-20250929",
anthropicApiKey: process.env.ANTHROPIC_API_KEY,
});
// Create the prompt
const prompt = ChatPromptTemplate.fromMessages([
["system", "You are a helpful assistant that can make API calls on behalf of the user. When making API calls, always use the signed_api_call tool to ensure requests are verifiable."],
["human", "{input}"],
["placeholder", "{agent_scratchpad}"],
]);
// Create the agent with the signed API tool
const agent = createToolCallingAgent({
llm: model,
tools: [signedApiCall],
prompt,
});
const executor = new AgentExecutor({
agent,
tools: [signedApiCall],
});
// Run the agent
const result = await executor.invoke({
input: "Check my account balance at https://api.example.com/balance",
});
console.log(result.output);Multiple Tools Example
Combine signed API calls with other tools:
import { tool } from "@langchain/core/tools";
import { z } from "zod";
import { signedApiCall } from "./tools/signed-api-call";
// A simple calculation tool
const calculateTool = tool(
async ({ expression }) => {
return String(eval(expression));
},
{
name: "calculate",
description: "Evaluate a mathematical expression",
schema: z.object({
expression: z.string().describe("The math expression to evaluate"),
}),
}
);
// Create agent with multiple tools
const agent = createToolCallingAgent({
llm: model,
tools: [signedApiCall, calculateTool],
prompt,
});
const executor = new AgentExecutor({
agent,
tools: [signedApiCall, calculateTool],
});
// Agent can now make signed API calls AND do calculations
const result = await executor.invoke({
input: "Get my balance from https://api.example.com/balance and calculate 10% of it",
});Specialized API Tools
For cleaner code, create domain-specific tools:
import { tool } from "@langchain/core/tools";
import { z } from "zod";
import { signHttpRequest, importPrivateKey } from "@belticlabs/agent-signer";
// Payments API tool
export const paymentsApi = tool(
async ({ action, amount, recipient }) => {
const privateKey = await importPrivateKey(process.env.AGENT_PRIVATE_KEY!, "EdDSA");
const url = "https://api.payments.com/v1/transactions";
const body = JSON.stringify({ action, amount, recipient });
const signedHeaders = await signHttpRequest({
method: "POST",
url,
headers: { "content-type": "application/json" },
body,
privateKey,
keyId: process.env.AGENT_KEY_ID!,
keyDirectoryUrl: process.env.AGENT_KEY_DIRECTORY_URL!,
});
const response = await fetch(url, {
method: "POST",
headers: signedHeaders,
body,
});
return response.json();
},
{
name: "payments_api",
description: "Interact with the payments API to check balance or send money",
schema: z.object({
action: z.enum(["balance", "send"]).describe("The action to perform"),
amount: z.number().optional().describe("Amount for send action"),
recipient: z.string().optional().describe("Recipient for send action"),
}),
}
);Server-Side Verification
The receiving service verifies requests using @belticlabs/verifier:
import express from "express";
import { createAgentAuthMiddleware, scopeGuard } from "@belticlabs/verifier/express";
const app = express();
app.use(express.json());
// Add Beltic verification middleware
app.use("/v1", createAgentAuthMiddleware());
// Protect routes with scope requirements
app.post("/v1/transactions", scopeGuard("payments:write"), (req, res) => {
const { action, amount, recipient } = req.body;
// req.agent contains verified agent info
console.log("Verified agent:", req.agent.id);
console.log("Developer:", req.agent.developer.legalName);
if (action === "balance") {
return res.json({ balance: 1000 });
}
if (action === "send") {
return res.json({ success: true, transactionId: "tx_123" });
}
res.status(400).json({ error: "Invalid action" });
});
app.listen(3000);Environment Variables
Set these environment variables:
# Anthropic API key for LangChain
export ANTHROPIC_API_KEY="sk-ant-..."
# Your agent's private key (PEM format)
export AGENT_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----"
# JWK thumbprint of your public key
export AGENT_KEY_ID="S9Zz0..."
# URL where your key directory is hosted
export AGENT_KEY_DIRECTORY_URL="https://your-agent.com/.well-known/http-message-signatures-directory"Error Handling
Add proper error handling to your tools:
export const signedApiCall = tool(
async ({ method, url, body, headers }) => {
try {
const privateKey = await importPrivateKey(privateKeyPem, "EdDSA");
const signedHeaders = await signHttpRequest({
method,
url,
headers: { "content-type": "application/json", ...headers },
body: body ? JSON.stringify(body) : undefined,
privateKey,
keyId,
keyDirectoryUrl,
});
const response = await fetch(url, {
method,
headers: { ...headers, ...signedHeaders },
body: body ? JSON.stringify(body) : undefined,
});
if (!response.ok) {
return JSON.stringify({
error: true,
status: response.status,
message: await response.text(),
});
}
return JSON.stringify({
status: response.status,
data: await response.json(),
});
} catch (error) {
return JSON.stringify({
error: true,
message: error instanceof Error ? error.message : "Unknown error",
});
}
},
{
name: "signed_api_call",
description: "Make a signed HTTP request to an external API",
schema: z.object({
method: z.enum(["GET", "POST", "PUT", "DELETE"]),
url: z.string().url(),
body: z.record(z.unknown()).optional(),
headers: z.record(z.string()).optional(),
}),
}
);Next Steps
- Trust Chain Verification - Understand the verification flow
- API Reference - Full SDK documentation
- Express Middleware - Server-side integration