Integration Guide

How to add amesh to your existing application. Pick the recipe that matches your setup.

Architecture Overview

Pairing (one-time)
Your Server (target)  <--WebSocket-->  Relay  <--WebSocket-->  Client (controller)
Both sides verify a 6-digit code, then exchange public keys.
Trust is one-way: controller → target. The relay can be shut down after this.
Runtime (every request)
Controller  ----HTTP + AuthMesh header---->  Target
One-way. No relay. Stateless headers. Target cannot call back.

Recipe 1: Protect an Express API

Add amesh signing and verification to an Express API.

Server

npm install @authmesh/sdk express
// server.ts
import express from 'express';
import { amesh } from '@authmesh/sdk';

const app = express();
app.use(express.text({ type: '*/*' }));

// Checks signature, timestamp, nonce, allow list
app.use('/api', amesh.verify());

app.post('/api/orders', (req, res) => {
  res.json({
    device: req.authMesh.deviceId,
    name: req.authMesh.friendlyName,
  });
});

app.listen(3000);

Client

// client.ts
import { amesh } from '@authmesh/sdk';

const res = await amesh.fetch('http://localhost:3000/api/orders', {
  method: 'POST',
  body: JSON.stringify({ amount: 100 }),
});

Initial setup (run once per machine)

# Install CLI
npm install -g @authmesh/cli

# Create identity on each machine
amesh init --name "prod-api"

# Pair: server is the target, your laptop is the controller
amesh listen          # on server (target)
amesh invite 482916   # on laptop (controller — use code from listen)

# Done. Trust is one-way: laptop → server. Relay can be stopped.

Pairing Remote Machines

When your server is in the cloud, both machines need to reach the same relay.

Option A: Public relay (easiest)

# On the remote server (SSH in)
amesh listen --relay wss://relay.authmesh.dev/ws

# On your laptop
amesh invite 482916 --relay wss://relay.authmesh.dev/ws

Option B: Run relay on the server

# On the remote server
bunx @authmesh/relay                   # starts on :3001
amesh listen --relay ws://localhost:3001/ws

# On your laptop
amesh invite 482916 --relay ws://your-server:3001/ws

Option C: Bootstrap token (non-interactive)

# On your laptop — generate a token
amesh provision --name "prod-server" --ttl 1h

# Set on remote server as env var. SDK auto-pairs on first request.
AMESH_BOOTSTRAP_TOKEN=eyJ... node app.js

Recipe 2: Microservices

Each service gets its own device identity. Pair once, authenticate every request.

// Service B (the API)
app.use(amesh.verify());

app.get('/internal/users/:id', (req, res) => {
  console.log(`Request from ${req.authMesh.friendlyName}`);
  res.json({ id: req.params.id });
});

// Service A (the caller)
const res = await amesh.fetch('http://service-b:4000/internal/users/123');

Recipe 3: Redis Nonce Store (Production)

For multi-instance deployments behind a load balancer. Prevents replay attacks across instances.

import { amesh } from '@authmesh/sdk';
import { RedisNonceStore } from '@authmesh/sdk/redis';

app.use(amesh.verify({
  nonceStore: new RedisNonceStore(process.env.REDIS_URL),
}));

Recipe 4: Webhooks

Sign webhooks with device identity instead of a shared secret.

// Sending
await amesh.fetch(webhookUrl, {
  method: 'POST',
  body: JSON.stringify(event),
});

// Receiving
app.post('/webhooks', amesh.verify(), (req, res) => {
  console.log(`Webhook from ${req.authMesh.friendlyName}`);
  res.sendStatus(200);
});

Local Development

Use the same code paths in local dev as production. No environment checks needed.

Setup

# Create local identities (encrypted-file backend for dev)
AUTH_MESH_DIR=/tmp/amesh-a amesh init --name "local-service-a" --backend encrypted-file
AUTH_MESH_DIR=/tmp/amesh-b amesh init --name "local-service-b" --backend encrypted-file

# Pair them (same ceremony as production)
AUTH_MESH_DIR=/tmp/amesh-b amesh listen          # on service-b (target)
AUTH_MESH_DIR=/tmp/amesh-a amesh invite 482916   # on service-a (controller)

Same code, dev and production

// Same code in dev and production. No environment checks.
import {'{'} amesh {'}'} from '@authmesh/sdk';

const res = await amesh.fetch('http://localhost:4000/api/data', {'{'}
  method: 'POST',
  body: JSON.stringify({'{'} query: 'test' {'}'})
{'}'});

Production: macOS Keychain, Secure Enclave, or TPM 2.0
Local dev: --backend encrypted-file (passphrase auto-generated)

Environment Variables

AUTH_MESH_DIR
Directory for identity and keys Default: ~/.amesh/
AUTH_MESH_PASSPHRASE
Override auto-generated passphrase (rarely needed) Default: optional
AMESH_BOOTSTRAP_TOKEN
Bootstrap token for automated pairing Default: optional
AMESH_RELAY_URL
WebSocket relay URL Default: wss://relay.authmesh.dev/ws
REDIS_URL
Redis URL for nonce store Default: optional

TypeScript Types

// Available on req.authMesh after amesh.verify()
interface AuthMeshIdentity {
  deviceId: string;       // "am_cOixWcOdI8-pLh4P"
  friendlyName: string;   // "prod-api"
  verifiedAt: number;     // Unix timestamp
}

// amesh.verify() options
interface VerifyOptions {
  clockSkewSeconds?: number;    // Default: 30
  nonceWindowSeconds?: number;  // Default: 60
  nonceStore?: NonceStore;      // Default: InMemoryNonceStore
}

Troubleshooting

"unauthorized" on every request
Check: (1) devices are paired (amesh list), (2) clocks are within 30s, (3) body is parsed as text (express.text()), not JSON.
"allow_list_integrity_failure" (500)
The allow list was modified outside amesh. Re-pair devices to regenerate.
"Using in-memory nonce store" warning
Production multi-instance deployments need Redis. See Recipe 3 above.