Adapters
How to integrate @ledgergate/ledgergate-sdk with Express and Fastify, including advanced usage and accessing request context in route handlers.
Overview
The SDK ships with two framework adapters out of the box:
createExpressMiddleware— Express 4 and 5fastifyLedgergate— Fastify 5 (registered as a plugin viafastify-plugin)
Both adapters implement the same lifecycle:
- Sampling check — skip all SDK work if not sampled
- Build a
RequestContext(UUID, redacted headers, hashed IP, timer) - Emit a
request.receivedevent - After the response finishes, detect x402 signals and emit the appropriate completion event
All adapter logic is wrapped in try-catch — an error inside the SDK will never propagate to your application.
Express
Basic setup
import express from "express";
import { createLedgergateSdk, createExpressMiddleware } from "@ledgergate/ledgergate-sdk";
const sdk = createLedgergateSdk({
apiKey: process.env.LEDGERGATE_API_KEY!,
});
const app = express();
// Register before your route handlers
app.use(createExpressMiddleware(sdk));
app.get("/api/v1/price", (_req, res) => {
res.json({ price: "100 SATS" });
});
app.listen(3000);Graceful shutdown
Always call sdk.shutdown() before your process exits. This flushes any events still buffered in-memory:
process.on("SIGTERM", async () => {
await sdk.shutdown();
process.exit(0);
});
process.on("SIGINT", async () => {
await sdk.shutdown();
process.exit(0);
});Accessing request context in handlers
The middleware stores the RequestContext on res.locals.x402Context. You can read it from any downstream handler or middleware:
import type { RequestContext } from "@ledgergate/ledgergate-sdk";
app.get("/api/data", (req, res) => {
const context = res.locals.x402Context as RequestContext | undefined;
console.log("Request ID:", context?.id);
res.json({ ok: true });
});Providing a response body for x402 detection
If you configure x402.source: "body" or "both", you need to attach the parsed JSON body to res.locals.x402Body before the response finishes:
app.get("/api/paid", (_req, res) => {
const paymentBody = {
"x-payment-address": "bc1qexample...",
"x-payment-amount": "1000",
"x-payment-network": "bitcoin",
"x-payment-token": "BTC",
};
// Attach so the SDK can read it on the 'finish' event
res.locals.x402Body = paymentBody;
res.status(402).json(paymentBody);
});Fastify
Basic setup
import Fastify from "fastify";
import { createLedgergateSdk, fastifyLedgergate } from "@ledgergate/ledgergate-sdk";
const sdk = createLedgergateSdk({
apiKey: process.env.LEDGERGATE_API_KEY!,
});
const app = Fastify();
// Register before your routes
await app.register(fastifyLedgergate, { sdk });
app.get("/api/v1/price", async () => {
return { price: "100 SATS" };
});
await app.listen({ port: 3000 });The plugin is wrapped with fastify-plugin so its onRequest and onResponse hooks apply globally, even to routes registered in other Fastify scopes.
Graceful shutdown
process.on("SIGTERM", async () => {
await app.close(); // closes Fastify and awaits in-flight requests
await sdk.shutdown(); // flushes remaining events
process.exit(0);
});Accessing request context in handlers
The middleware decorates each FastifyRequest with a x402Context property:
import type { RequestContext } from "@ledgergate/ledgergate-sdk";
app.get("/api/data", async (request) => {
const context: RequestContext | undefined = request.x402Context;
console.log("Request ID:", context?.id);
return { ok: true };
});TypeScript knows about this property automatically because the Fastify adapter extends the FastifyRequest interface via module augmentation.
Providing a response body for x402 detection
Attach the parsed body to request.x402Body inside your handler before returning:
app.get("/api/paid", async (request, reply) => {
const paymentBody = {
"x-payment-address": "bc1qexample...",
"x-payment-amount": "1000",
"x-payment-network": "bitcoin",
"x-payment-token": "BTC",
};
// Attach so the SDK reads it in the onResponse hook
request.x402Body = paymentBody;
return reply.status(402).send(paymentBody);
});Lifecycle Diagram
Request arrives
│
▼
┌─────────────────────────────────┐
│ Adapter middleware / onRequest │
│ 1. shouldSample() │
│ 2. createRequestContext() │
│ 3. enqueue(request.received) │
└────────────────┬────────────────┘
│
▼
Your route handler
│
▼
┌─────────────────────────────────┐
│ Response finish / onResponse │
│ 4. captureResponseData() │
│ 5. detectX402() │
│ 6. enqueue(payment.* or │
│ request.completed) │
└────────────────┬────────────────┘
│
▼
EventQueue (async)TypeScript Reference
import {
createExpressMiddleware,
fastifyLedgergate,
type FastifyLedgergateOptions,
type SdkInstance,
} from "@ledgergate/ledgergate-sdk";| Export | Description |
|---|---|
createExpressMiddleware(sdk) | Returns an Express RequestHandler |
fastifyLedgergate | Fastify plugin (FastifyPluginAsync) |
FastifyLedgergateOptions | { sdk: SdkInstance } |
SdkInstance | Interface for the object returned by createLedgergateSdk() |