Skip to main content
The Hono integration provides two exports from @key0ai/key0/hono:
  • key0App(opts) — a Hono sub-app that serves agent discovery and the x402 payment endpoint.
  • honoValidateAccessToken({ secret }) — middleware that validates Bearer JWTs issued by Key0.

Installation

bun add @key0ai/key0 hono

Routes

When you mount key0App, the following routes are registered:
MethodPathDescription
GET/.well-known/agent.jsonA2A agent card (discovery)
GET/discoverPlan catalog (all plans and pricing)
POST/x402/accessUnified x402 HTTP payment endpoint

Setup

1

Configure the seller

Define your SellerConfig with wallet address, network, agent identity, and pricing plans.
import { AccessTokenIssuer } from "@key0ai/key0";
import type { SellerConfig } from "@key0ai/key0";

const SECRET = process.env.KEY0_ACCESS_TOKEN_SECRET!;
const tokenIssuer = new AccessTokenIssuer(SECRET);

const sellerConfig: SellerConfig = {
  agentName: "My API Agent",
  agentDescription: "Pay-per-use API access via USDC on Base",
  agentUrl: "https://api.example.com",
  providerName: "Example Corp",
  providerUrl: "https://example.com",
  walletAddress: "0xYourWalletAddress" as `0x${string}`,
  network: "testnet",
  plans: [
    {
      planId: "basic",
      unitAmount: "$0.10",
      description: "Basic API access",
    },
  ],
  fetchResourceCredentials: async (params) => {
    return tokenIssuer.sign(
      {
        sub: params.requestId,
        jti: params.challengeId,
        resourceId: params.resourceId,
        planId: params.planId,
        txHash: params.txHash,
      },
      3600,
    );
  },
};
2

Create stores and adapter

Provide the required IChallengeStore, ISeenTxStore, and IPaymentAdapter instances.
import {
  RedisChallengeStore,
  RedisSeenTxStore,
  X402Adapter,
} from "@key0ai/key0";
import Redis from "ioredis";

const redis = new Redis();
const store = new RedisChallengeStore({ redis });
const seenTxStore = new RedisSeenTxStore({ redis });
const adapter = new X402Adapter({ network: "testnet" });
3

Mount the sub-app

Import key0App from @key0ai/key0/hono and mount it on your main Hono app.
import { Hono } from "hono";
import { key0App } from "@key0ai/key0/hono";

const app = new Hono();

app.route(
  "/",
  key0App({
    config: sellerConfig,
    adapter,
    store,
    seenTxStore,
  }),
);
Your agent card is now available at GET /.well-known/agent.json and the payment endpoint at POST /x402/access.
4

Protect routes with access token validation

Use honoValidateAccessToken as middleware on any route that requires a valid Key0 JWT.
import { honoValidateAccessToken } from "@key0ai/key0/hono";

const guard = honoValidateAccessToken({
  secret: SECRET,
});

app.get("/api/resource", guard, (c) => {
  const token = c.get("key0Token");
  return c.json({
    message: "Authenticated",
    challengeId: token.jti,
    planId: token.planId,
    txHash: token.txHash,
  });
});

Accessing the decoded token

After honoValidateAccessToken succeeds, the decoded JWT payload is available via c.get("key0Token"). The payload contains:
FieldTypeDescription
substringThe original request ID
jtistringThe challenge ID
resourceIdstringThe resource that was paid for
planIdstringThe plan the client purchased
txHashstringThe on-chain transaction hash
On failure, the middleware returns a JSON error response:
ScenarioHTTP StatusError Code
Missing/malformed header401INVALID_REQUEST
Expired token401CHALLENGE_EXPIRED
Invalid signature401INVALID_REQUEST
Internal error500INTERNAL_ERROR

Full example

import { Hono } from "hono";
import { serve } from "@hono/node-server";
import {
  AccessTokenIssuer,
  RedisChallengeStore,
  RedisSeenTxStore,
  X402Adapter,
} from "@key0ai/key0";
import { key0App, honoValidateAccessToken } from "@key0ai/key0/hono";
import Redis from "ioredis";

const redis = new Redis();
const SECRET = process.env.KEY0_ACCESS_TOKEN_SECRET!;
const tokenIssuer = new AccessTokenIssuer(SECRET);

const sellerConfig = {
  agentName: "My API Agent",
  agentDescription: "Pay-per-use API access via USDC on Base",
  agentUrl: "https://api.example.com",
  providerName: "Example Corp",
  providerUrl: "https://example.com",
  walletAddress: process.env.KEY0_WALLET_ADDRESS! as `0x${string}`,
  network: "testnet" as const,
  plans: [
    {
      planId: "basic",
      unitAmount: "$0.10",
      description: "Basic API access",
    },
  ],
  fetchResourceCredentials: async (params: {
    requestId: string;
    challengeId: string;
    resourceId: string;
    planId: string;
    txHash: string;
  }) => {
    return tokenIssuer.sign(
      {
        sub: params.requestId,
        jti: params.challengeId,
        resourceId: params.resourceId,
        planId: params.planId,
        txHash: params.txHash,
      },
      3600,
    );
  },
};

const app = new Hono();

// Mount Key0 sub-app (agent card + x402 payment endpoint)
app.route(
  "/",
  key0App({
    config: sellerConfig,
    adapter: new X402Adapter({ network: "testnet" }),
    store: new RedisChallengeStore({ redis }),
    seenTxStore: new RedisSeenTxStore({ redis }),
  }),
);

// Protected route
const guard = honoValidateAccessToken({ secret: SECRET });

app.get("/api/resource", guard, (c) => {
  const token = c.get("key0Token");
  return c.json({ resource: "data", plan: token.planId });
});

serve({ fetch: app.fetch, port: 3000 });
console.log("Hono server running on http://localhost:3000");

API reference

key0App(opts)

Creates a Hono sub-app with agent card discovery and the x402 payment endpoint.
function key0App(opts: Key0Config): Hono
Parameters:
FieldTypeDescription
configSellerConfigSeller configuration (wallet, plans)
adapterIPaymentAdapterOn-chain payment verification adapter
storeIChallengeStoreChallenge state storage
seenTxStoreISeenTxStoreDouble-spend prevention storage

honoValidateAccessToken(config)

Returns Hono middleware that validates a Bearer token from the Authorization header.
function honoValidateAccessToken(config: ValidateAccessTokenConfig): MiddlewareHandler
Parameters:
FieldTypeDescription
secretstringThe JWT secret used to sign tokens

Pay-Per-Request Routes

key0App returns the Hono app and a payPerRequest(routeId, opts?) factory. Use it as Hono middleware on individual routes to gate them behind micro-payments — no JWT issued, payment settles inline.
const key0 = key0App({
  config: {
    // ...
    routes: [
      {
        routeId: "weather-query",
        method: "GET",
        path: "/api/weather/:city",
        unitAmount: "$0.01",
      },
    ],
    fetchResourceCredentials: async (params) => tokenIssuer.sign(params, 3600),
  },
  adapter, store, seenTxStore,
});

const app = new Hono();
app.route("/", key0);

// Gate the route — every call needs a PAYMENT-SIGNATURE header
app.get("/api/weather/:city", key0.payPerRequest("weather-query"), (c) => {
  const payment = c.get("key0Payment"); // PaymentInfo
  return c.json({ city: c.req.param("city"), temp: 72, txHash: payment?.txHash });
});
Without a PAYMENT-SIGNATURE header the middleware returns 402 with x402 payment requirements. After settlement, the PaymentInfo is available via c.get("key0Payment"). For standalone gateway mode, add proxyTo to SellerConfig — see the Express integration for the full proxy configuration reference.

Next Steps

SellerConfig Reference

Complete reference for every configuration option.

Paying for Access

How clients discover, pay, and call your protected endpoints.

Express Integration

Full A2A JSON-RPC support is available on Express only.

x402 HTTP Flow

Full protocol reference for the payment flow.